(转)二叉树的存储方式【顺序储存(数组)、链式存储、邻接表存储等】

发布时间 2023-09-22 16:38:59作者: liujiacai

原文:https://blog.csdn.net/qq_21989927/article/details/108666433

1.顺序存储结构
二叉树的顺序存储,就是用一组连续的存储单元存放二叉树中的结点。因此,必须把二叉树的所有结点安排成为一个恰当的序列,结点在这个序列中的相互位置能反映出结点之间的逻辑关系,用编号的方法从树根起,自上层至下层,每层自左至右地给所有结点编号,缺点是有可能对存储空间造成极大的浪费,在最坏的情况下,一个深度为k且只有k个结点的右单支树需要2k-1个结点存储空间。

依据二叉树的性质,完全二叉树和满二叉树采用顺序存储比较合适,树中结点的序号可以唯一地反映出结点之间的逻辑关系,这样既能够最大可能地节省存储空间,又可以利用数组元素的下标值确定结点在二叉树中的位置,以及结点之间的关系。

对于一般的二叉树,如果仍按从上至下和从左到右的顺序将树中的结点顺序存储在一维数组中,则数组元素下标之间的关系不能够反映二叉树中结点之间的逻辑关系,只有增添一些并不存在的空结点,使之成为一棵完全二叉树的形式,然后再用一维数组顺序存储。

例子,如图a,b,c:

 

(a) 一棵二叉树 (b) 改造后的完全二叉树

 

(c) 改造后完全二叉树顺序存储状态

显然,这种存储对于需增加许多空结点才能将一棵二叉树改造成为一棵完全二叉树的存储时,会造成空间的大量浪费,不宜用顺序存储结构。

2.链式存储结构
struct Node{
int data;
Node* leftchild;
Node* rightchild;
};
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。

通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。其结点结构为:

 

其中,data域存放某结点的数据信息;lchild与rchild分别存放指向左孩子和右孩子的指针,当左孩子或右孩子不存在时,相应指针域值为空(用符号∧或NULL表示)。利用这样的结点结构表示的二叉树的链式存储结构被称为二叉链表。

例子如图:

 

(a) 一棵二叉树 (b) 二叉链表存储结构

为了方便访问某结点的双亲,还可以给链表结点增加一个双亲字段parent,用来指向其双亲结点。每个结点由四个域组成,其结点结构为:

 

这种存储结构既便于查找孩子结点,又便于查找双亲结点;但是,相对于二叉链表存储结构而言,它增加了空间开销。利用这样的结点结构表示的二叉树的链式存储结构被称为三叉链表。

3.二维数组直接存储
若是题目直接给出了每个节点的左右孩子,可以直接用二维数组存储,不用建树,在遍历以及其他操作时,可以直接使用这个二维数组。

例题:洛谷求二叉树的深度 https://www.luogu.com.cn/problem/P4913

 

 

代码:

#include<iostream>
using namespace std;

const int MaxN=1000050;
int son[MaxN][2];

int dfs(int x){
if (x==0) return 0;
int l=dfs(son[x][0]);
int r=dfs(son[x][1]);
return max(l,r)+1;
}

int main(){
int n;
cin>>n;
for (int i=1; i<=n; i++){
cin>>son[i][0]>>son[i][1];
}
cout<<dfs(1)<<endl;
}
4.邻接表存储
假设题目输入中,我们只知道 x , y 之间有一条边,但是并不知道 x , y 的父子关系的时候,可以使用邻接表的方法存储树。
这时候把树看做一个图,建边要建双向边,然后在从根做dfs,确定每个节点的深度,顺便也可以求出每个节点的父亲节点,这样节点之间的父子关系就清楚了。

例题:
第一行输入N、root,表示这棵树有N个节点,根为root。
接下来 N-1 行每行包含两个正整数 x, y,表示 x 结点和 y 结点之间有一条直接连接的边(数据保证可以构成树)。

求每个节点的深度和每个节点的父亲节点。

代码:

#include<iostream>
using namespace std;

const int MaxN=500050;
struct Edge{
int v;
int next;
};
Edge e[MaxN];
int last[MaxN];
int n,m,root,tot;
int deep[MaxN];
int f[MaxN];

void build(int x,int y){
tot++;
e[tot].v=y;
e[tot].next=last[x];
last[x]=tot;
}

//编号为x的节点,父亲是fa
void dfs(int x,int fa){
f[x]=fa;
deep[x]=deep[fa]+1;
for (int j=last[x]; j!=0; j=e[j].next){
int y=e[j].v;
if (y!=fa) dfs(y,x);
}
}


int main(){
cin>>n>>root;
for (int i=1; i<=n-1; i++){
int x,y;
cin>>x>>y;
build(x,y);
build(y,x);
}
dfs(root,0);
for (int i=1; i<=n; i++) cout<<deep[i]<<" "; cout<<endl;
for (int i=1; i<=n; i++) cout<<f[i]<<" "; cout<<endl;
}
此种方法不仅适合二叉树,也适合多叉树。
————————————————
版权声明:本文为CSDN博主「马小超i」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_21989927/article/details/108666433