七、数据结构(二叉树基础)

什么是二叉树

试想一下,我们怎么样才能计算一颗苹果树上有多少颗苹果呢?
其实我们只需要知道左边树权的苹果数量和右边树权的苹果数量,然后计算它们的和就行了。那左边树杈有多少个苹果呢?可以使用一样的方法来统计,把这个分杈当作树干,然后统计这个树干的左边树杈和右边树杈的苹果数量之和……直到统计到树枝末端的每一个苹果,然后依次汇总就可以得到苹果的数量。

显然,树结构不仅能表示数据间的指向关系,还能表示出数据的层次关系,而有很明显的递归性。因此,可以利用树的性质解决更多种类的问题。

二叉树是一种特殊的树,每次分叉不超过两部分。二叉树作为数据结构是非常重要和基础的。无论是二叉堆、线段树还是平衡树,这些高级的数据结构都以二叉树为基础。(下图是一颗树结构)

解释说明:

  1. 每个节点的左右两个节点分别称为这棵树的左儿子和右儿子。
  2. 以节点左儿子为根节点的树称为这个节点的左子树,右边同样。
  3. 如果一个结点没有任何子树,那就称为叶子结点(8、9、10……)
  4. 这个图一共有4层结点,所以这个二叉树的高度是4。
  5. 如果一个二叉树的高度为 H H H,从第二层开始每一层的结点数都是上一层的两倍。一共有 2 H − 1 2^H-1 2H1个结点的二叉树称为完美二叉树

有没有发现一些规律?对于 i i i 号非叶子结点,它的左子树编号是 2 i 2i 2i 右子树的编号是 2 i + 1 2i + 1 2i+1。由此,我们可以创建若干个足够大的数组,将各个结点的信息记录进去,通过计算编号来访问左右子树,并使用递归的方式得到各个子树的信息。

基础操作

这里比较内容比较冗杂,沉住气一步一步来。

1.从叶子节点反推每一层的状态

问题描述
现有一颗完美二叉树,它每个节点的值都等于他左右儿子的值之和。
现告诉你这棵树有多高,然后从左到右给出叶子节点的权值,请你还原这棵树。

分析
如果我们要用一个数组记录这棵树的节点信息,如下图:

一个有 n n n 层的完美二叉树有多少个叶子节点呢? 2 n − 1 2^{n - 1} 2n1个。
所以我们可以直接从数组的第 2 n − 2 n − 1 2^n-2^{n-1} 2n2n1 项开始输入,这样就能直接录入最后一层的信息了。

  • 我们可以用位运算表示 2 n 2^n 2n : (1 << n)
    现在我们有了录入部分的代码:
for(int i = 0, i < (1 << (n - 1)); i++){
	cin >> node[i + (1 << (n - 1))];
}

然后我们可以通过搜索来体现每个节点的值了

void dfs(int x){  
	if(x >= 1 << (n - 1))return ;
	//到达叶子节点之后就开始返回  
	dfs(x * 2);  
	//进入左子树
	dfs(x * 2 + 1);  
	//进入右子树
	node[x] = node[x*2] + node[x * 2 + 1];  
	//整合每个节点
}

演示程序:

int node[1000];
int n;
void dfs(int x){
    if(x >= 1 << (n - 1))return ;
    dfs(x * 2);
    dfs(x * 2 + 1);
    node[x] = node[x*2] + node[x * 2 + 1];
}
int main(){
    cin >> n;
    for(int i = 0; i < (1 << (n - 1)); i++){
        cin >> node[i + (1 << (n -1))];
    }
    dfs(1);
    int cnt = 1;
    for(int i = 1; i <= n; i++){
        int t = 1 << (i - 1);
        while(t--){
            cout << node[cnt++] <<" ";
        }
        cout << endl;
    }
    return 0;
}

2.建立一棵二叉树

如果我们要建立一棵树,就得先考虑每个节点需要有什么,最基本的就是能表现出左右儿子在哪,节点权值是多少
所以我们用这样一个结构体来记录节点,-1表示没有左/右儿子:

struct node{
	int val, left, right;
	node(){
	val = 0, left = -1, right = -1;
	}
};
node t[N];

每一次我们输入一个节点的权值, 左儿子位置, 右儿子位置,这样就能建立一棵树了

void insert(int loc){
	cin >> t[loc].val >> t[loc].left >> t[loc].right;
}

3.一棵树的深度

接上文,我们可以通过DFS遍历一棵树来获得一棵树的深度

int T_deep = 0;
void dfs(int root, int deep){
	T_deep = max(T_deep, deep);
	if(t[root].left != -1){
		dfs(t[root].left, deep + 1);
	}
	if(t[root].right != -1){
		dfs(t[root].right, deep + 1);
	}
}

4.遍历一棵树

先序遍历
如果我们按照“中–左–右”的顺序遍历一棵树,这就是先序遍历。

void preOrder(int root){
	cout << t[root].val << " ";
	if(t[root].left != -1)preOrder(t[root].left);
	if(t[root].right != -1)preOrder(t[root].right);
}

同理,我们调换三行的顺序,就可以实现中序遍历和后续遍历。
这些遍历的原理和深度优先遍历是一样的
层次遍历

void bfs(int root){
    queue<node>q;
    q.push(t[root]);
    while(!q.empty()){
        node tmp = q.front();
        q.pop();
        cout << tmp.val <<" ";
        if(tmp.left != -1)q.push(t[tmp.left]);
        if(tmp.right != -1)q.push(t[tmp.right]);
    }
}
  • 48
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fanxinfx2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值