【二叉树】第一章:相关性质与四种遍历

1. 二叉树的性质

  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 2^{i-1}-1 个结点.
  2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 2^h -1
  3. 对任何一棵二叉树, 如果度为 0 其叶结点个数为 n_0 , 度为2的分支结点个数为 n_2 , 则有 n_0=n_2+1
  4. 对任何一棵二叉树, 如果度为 0 其叶结点个数为 n_0 , 度为1的分支结点个数为 n_1 , 度为2的分支结点个数为 n_2 , N 为节点总数, 则有 n_0 + n_1 + n_2 == N
  5. 因为完全二叉树 的特殊结构(最后一层连续):则 度为1的分支结点个数为 n_1 最多存在 1 个
  6. 若规定根节点的层数为 1,具有 n 个结点的满二叉树的深度,h=log_2(n+1)
  7. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为  i  的结点有:
  • 若  i > 0,i位置节点的双亲序号:(i-1)/2;i=0,i 为根节点编号,无双亲节点
  • 若 2i+1 < n,左孩子序号:2i+1,2i+1>=n 否则无左孩子
  • 若 2i+2 < n,右孩子序号:2i+2,2i+2>=n 否则无右孩子

2. 二叉树链式结构的实现

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。此处手动快速创建一棵简单的二叉树(一个个节点手搓),快速进入二叉树 操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

这里创建二叉树:创造链式节点,再链接起来

树的形状取决于自己:自己链接

注意:下面代码并不是创建二叉树的真正方式,真正创建二叉树方式后续章节再详解重点讲解

本章节的所有代码操作都会以 下面这幅图的二叉树为 基础讲解:

// 链表节点
typedef int BTDataType;
typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* Left;
	struct BinaryTreeNode* Right;
	BTDataType value;
}BTNode;


//  创造节点  函数
BTNode* NodeCreate(BTDataType x) {
	BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
	if (newNode == NULL) {
		perror("malloc fail !");
		return NULL;
	}
    
	newNode->value = x;
	newNode->Left = newNode->Right = NULL;
	return newNode;
}


// 手搓一棵树:手动连接每一个节点
BTNode* TreeCreate()
{
	// 先建造 6 个节点
	BTNode* n1 = NodeCreate(1);
	BTNode* n2 = NodeCreate(2);
	BTNode* n3 = NodeCreate(3);
	BTNode* n4 = NodeCreate(4);
	BTNode* n5 = NodeCreate(5);
	BTNode* n6 = NodeCreate(6);

	// 自己定义链接方式:就是你想要树的形状
	n1->Left = n2;
	n2->Left = n3;
	n1->Right = n4;
	n4->Left = n5;
	n4->Right = n6;
    
	return n1; // 返回根节点
}
//

3. 二叉树的遍历

三种遍历方式的遍历顺序依次为:

前序遍历:根 -> 左节点 -> 右节点

中序遍历:左节点 -> 根 -> 右节点

后序遍历:左节点 -> 右节点 -> 根

注意:被访问的结点必是某子树的根! 牢记这句话,下面讲解三种遍历顺序,不理解时,回想这句话

每个节点本身 就是 根

3.1. 前序遍历:根 -> 左节点 -> 右节点

动图演示:前序遍历

这里 用 前序遍历演示过程,中序和后序遍历同理,仅仅是顺序不同

红色 代表 递推下去
绿色 代表 回退
遇到 NULL 就 回退,否则按照遍历顺序 递推下去

注意:2 到 3 的第二条线应该是 绿色(暂时没有改过来)

下面的 前序遍历过程中遍历到的 有效节点依次为:1 2 3 N N N 4 5 N N 6 N N

(N 表示 NULL, 同时 已经遍历过的节点 不重复记录:即绿色回退的节点不重复算)

前言:根据上面遍历演示:发现 一个根节点,需要遍历到下一个根节点,最后还需要回退回上一个根节点,则 此过程明显需要使用 递归

根据 递归过程,三种遍历方式均用递归实现:

3.2. 中序遍历:左节点 -> 根 -> 右节点

中序遍历的遍历到的 有效节点依次为:N 3 N 2 N 1 N 5 N 4 N 6 N

注意 第一个 N 是指:3 的左下角的 NULL

因为 每轮为了找到 “左节点”, 首先要 向左下角走,但每次遍历到的节点,都算作 根节点,所以不能直接访问记录,还要一直向下左下走,直到 NULL 回退,第一个NULL回退就到了 3 ,3 就是根节点,(因为:中序遍历:左节点 -> 根 -> 右节点)因此这一轮算做:从 左节点 到 根节点,算为有效访问,也就记录了 N -> 3,接着 向右下角走,遇到NULL,则算作 N -> 3 -> N;其他以此类推

3.3. 后序遍历:左节点 -> 右节点 -> 根

后序遍历的遍历到的 有效节点依次为:N N 3 N 2 N N 5 N N 6 4 1

遍历原理同上

 3.4. 代码实现如下

// 前序遍历:根 -> 左节点 -> 右节点
void PreOrder(BTNode* root)
{
	if (root == NULL) {
		printf("N ");
		return;
	}
    // 按顺序走递归流程
	printf("%d ", root->value);  // 根
	PreOrder(root->Left);   // 左节点
	PreOrder(root->Right);  // 右节点
}



// 中序遍历:左节点 -> 根 -> 右节点
void InOrder(BTNode* root)
{
	if (root == NULL) {
		printf("N ");
		return;
	}
    // 按顺序走递归流程
	InOrder(root->Left);   // 左节点
	printf("%d ", root->value);  // 根
	InOrder(root->Right);   // 右节点
}



// 后序遍历:左节点 -> 右节点 -> 根 
void PostOrder(BTNode* root)
{
	if (root == NULL) {
		printf("N ");
		return;
	}
    // 按顺序走递归流程
	PostOrder(root->Left);   // 左节点
	PostOrder(root->Right);   // 右节点
	printf("%d ", root->value);  // 根
}

递归图展开图:意会就好(以中序遍历为例)

层序遍历:(非递归)

层序遍历的本质就是实现 bfs 的队列思想(bfs实现、非递归):若不知道 bfs, 需要理解下面演示的 队列 使用方法即可,bfs 可以自己另外学习 

1.只打印数字

层序遍历的结果:  1  2 4   3 5 6

gif 动态演示图:
队列为空 就 入队
每次 一个节点 出队,就打印,同时,节点 出队 前 会将 左右子节点 入队
结束条件为 队列为 空
这样就能达到 一层一层遍历 的效果

BTNode* arr[2];
void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	if (root != NULL) {
		QueuePush(&q, root);
	}

	while (!QueueEmpty(&q)) {
		BTNode* tmp = QueueFront(&q);
		QueuePop(&q);
		printf("%d ", tmp->value);

		arr[0] = tmp->Left, arr[1] = tmp->Right;
		for (int i = 0; i < 2; ++i) {
			BTNode* t = arr[i];
			if (t == NULL) continue;
			QueuePush(&q, t);
		}
	}
	printf("\n");
	QueueDestory(&q);
}

int main()
{
	BTNode* root = TreeCreate(); // 先造一棵树(默认我之前的树)
	LevelOrder(root);
	return 0;
}

2.若想打印出 空N ,稍微改一下就好
层序遍历的结果:1 2 4 3 N 5 6 N N N N N N
需要修改的关键代码
if (tmp != NULL) printf("%d ", tmp->value);
else {
    printf("N ");
    continue;
}
BTNode* arr[2];
void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	if (root != NULL) {
		QueuePush(&q, root);
	}

    
	while (!QueueEmpty(&q)) {
		BTNode* tmp = QueueFront(&q);
		QueuePop(&q);
		if (tmp != NULL) printf("%d ", tmp->value);
		else {
			printf("N ");
			continue;
		}

		arr[0] = tmp->Left, arr[1] = tmp->Right;
		for (int i = 0; i < 2; ++i) {
			BTNode* t = LR[i];
			//if (t == NULL) continue;
			QueuePush(&q, t);
		}
	}
    
    
	printf("\n");
	QueueDestory(&q);
}

对于四种遍历方法:初学者建议 把遍历到 NULL 也写出来(打印一个 N),直接全写数字容易理解不够透彻,不要直接全部写数字:如后序遍历直接写 325641

【若文章有什么错误,欢迎评论区讨论或私信指出】 

  • 36
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值