请回答数据结构【二叉树和堆(下)】
1. Before BinaryTree
二叉树操作回顾二叉树的结构表示和概念
1.0 二叉树结构表示(二叉链)
1.1 二叉树的概念
二叉树是:
- 空树
- 非空:根节点,根节点的左子树、根节点的右子树组成的。
1.2 二叉树有什么不同?
二叉树加上算法可以生成哈夫曼树,产生的哈夫曼编码可以用于文件压缩
二叉树真正有意义的是搜索,也就是产生搜索树
这样的树可能就是左边比父亲小,右边比父亲大
找一个节点遍历的次数最多就只有高度次,效率很高
但是极端情况下效率退化成O(N),那么还是和链表差不多,怎么解决呢?于是产生了平衡树,也就是红黑树和AVL树
2. 二叉树的遍历
二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉
树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
2.1 四种遍历顺序:
按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
- 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
- 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
- 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
遍历方式 | 遍历顺序 |
---|---|
前序遍历也叫先根遍历 | 根->左子树->右子树 |
中序遍历也叫中根遍历 | 左子树->根->右子树 |
后序遍历也叫后根遍历 | 左子树->右子树->根 |
层序遍历 | h=1,2,3,4… |
2.2 诸如此例
比如说像下面这样一个树
前序遍历 | A->B->D->NULL->NULL->NULL->C->E->NULL->NULL->F->NULL->NULL |
---|---|
中根遍历 | NULL->D->NULL->B->NULL->A->NULL->E->NULL->C->NULL->F->NULL |
后根遍历 | NULL->NULL->D->NULL->B->NULL->NULL->E->NULL->NULL->F->C->A |
2.3 前序的递归解释:根->左子树->右子树
2.4 前序遍历图解
2.5 代码实现
2.5.1 实现之前
学习二叉树的基本操作前,需先要创建一棵二叉树,此处采用手动快速创建一棵简单的二叉树,快速进入二叉树操作
typedef int BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
BTDataType data;
}BTNode;
BTNode* BuyBTNode(BTDataType x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
printf("malloc fail\n");
exit(-1);
}
node->data = x;
node->left = node->right = NULL;
return node;
}
BTNode* CreatBinaryTree()
{
BTNode* node1 = BuyBTNode(1);
BTNode* node2 = BuyBTNode(2);
BTNode* node3 = BuyBTNode(3);
BTNode* node4 = BuyBTNode(4);
BTNode* node5 = BuyBTNode(5);
BTNode* node6 = BuyBTNode(6);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
2.5.2 PreOrder
void PrevOrder(BTNode* root) {
if (root == NULL) {
printf("NULL ");
return;
}
printf("%d ", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
思考调用递归的过程
2.5.3 Inorder
void InOrder(BTNode* root) {
if (root == NULL) {
printf("NULL ");
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
2.5.4 PostOrder
void PostOrder(BTNode* root)
{
if (root == NULL) {
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d", root->data);
}
这和之前诸如此例走的是一模一样的
2.5.5 TreeSize
下面的问题都涉及到了使用一种思想,也就是分治思想,将一个复杂问题,把它分成规模更小的问题,子问题再分成问题更小规模的子问题,直到子问题不能再分割,直接能出结果
2.5.5.1 想法一
//想法1
void BTreeSize(BTNode* root)
{
int count = 0;
if (root == NULL)
return ;
++count;
BTreeSize(root->left);
BTreeSize(root->right);
}
递归有很多的栈帧,导致每一个count都可能是不一样的,不可行
2.5.5.2 想法二
//想法2
int count = 0;
void BTreeSize(BTNode* root)
{
if (root == NULL)
return count;
++count;
BTreeSize(root->left);
BTreeSize(root->right);
}
全局变量不是存在栈帧上的,是存在静态区的,所以不会产生之前的问题
2.5.5.3 想法三
//想法3
int BTreeSize(BTNode* root)
{
static int count = 0;
if (root == NULL)
return count;
++count;
BTreeSize(root->left);
BTreeSize(root->right);
return count;
}
静态变量需要添加返回值,其实和全局变量类似
2.5.5.3 想法四
传地址就可以了
//想法四
//思想:遍历 + 计数
void BTreeSize(BTNode * root, int* pCount)
{
if (root == NULL)
return;
++(*pCount);
BTreeSize(root->left, pCount);
BTreeSize(root->right, pCount);
}
2.5.5.4 想法五
int BTreeSize(BTNode* root) {
return root == NULL ? 0 :
BTreeSize(root->left)
+ BTreeSize(root->right) + 1;
}
2.5.6 BTreeLeafSize
空 | return 0 |
---|---|
自己是叶子节点 | return 1 |
都不是 | return 左子树叶子节点+右子树叶子节点 |
int BTreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL && root->right == NULL)
return 1;
return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);
}
2.5.7 BTreeKLevelSize
求第k层的节点的个数,k >= 1
空树 | 返回0 |
---|---|
非空,且k==1 | 返回1 |
非空且k>1继续往下走往左边和右边递归 | 加起来计算总数 |
int BTreeKLevelSize(BTNode* root, int k)
{
assert(k >= 1);
if (root == NULL)
return 0;
if (k == 1)
return 1;
return BTreeKLevelSize(root->left, k - 1)
+ BTreeKLevelSize(root->right, k - 1);
}
2.5.8 BTreeDepth
获取左右子树中深度更深的子树,然后返回最大值即可
int BTreeDepth(BTNode* root)
{
if (root == NULL)
return 0;
int leftDepth = BTreeDepth(root->left);
int rightDepth = BTreeDepth(root->right);
return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
2.5.9 BTreeFind
空树 | return NULL | ||
---|---|---|---|
要找的不是根节点 | 往左树递归 | ||
左边没找到 | 往右树递归 | 左边找到了 | return 左子树 |
右边也没找到,返回NULL,然后往上走 | 重复判断 | 右边找到了 | return 右子树 |
// 二叉树查找值为x的结点
BTNode* BTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->data == x)
return root;
BTNode* ret1 = BTreeFind(root->left, x);
if (ret1)
return ret1;
//return BTreeFind(root->right, x);
BTNode* ret2 = BTreeFind(root->right, x);
if (ret2)
return ret2;
return NULL;
}
2.5.10 BTreeDestory
可以看成后序删除相当于先把child删除再删除根
// 二叉树销毁
void BTreeDestory(BTNode* root)
{
if (root == NULL)
{
return;
}
BTreeDestory(root->left);
BTreeDestory(root->right);
free(root);
}
2.6 二叉树的深度优先遍历与广度优先遍历
2.6.1 深度优先DFS
前序遍历属于深度优先遍历,是以走深度为主,如果无法再深了就回到之前
2.6.2 广度优先BFS
一层一层走,一般借助于队列来实现,比如说层序遍历,比如如图结构的遍历
-
先把根入队列,借助队列先进先出的性质
-
出队头的数据,把他的下一层传进去
特点:是队列先进先出的性质,然后上一层出的时候,带入下一层
//前置声明
struct BinaryTreeNode;
typedef struct BinaryTreeNode* QDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
BTDataType data;
}BTNode;
typedef struct BinaryTreeNode* QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
}Queue;
二叉树的广度优先遍历
// 层序遍历
void LevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
printf("%d ", front->data);
if (front->left)
{
QueuePush(&q, front->left);
}
if (front->right)
{
QueuePush(&q, front->right);
}
}
printf("\n");
QueueDestory(&q);
}
用纯C写确实非常复杂,还要自己写一个Queue,下面用cpp写一个试试看
#include <iostream>
#include<queue>
using std::queue;
using std::cout;
using std::endl;
namespace allen
{
void LevelOrder(BTNode* root)
{
queue<BTNode*> q;
//若根节点不为空,放入根
if (root)
{
q.push(root);
}
while (! q.empty())//依次拿出根的左右孩子,并cout
{
BTNode* front = q.front();
q.pop();//删除指向节点的指针
cout << front->data<<" ";
//放入左
if (front->left)
{
q.push(front->left);
}
//放入右
if (front->right)
{
q.push(front->right);
}
}
cout << endl;
}
}
2.6.3 BTreeComplete判断一颗树是不是完全二叉树
借助层序遍历的原理,来判断是不是完全二叉树
如果是层序排列走的话,那么节点应该是连续的,此时我们考虑空节点这次也进队列
思考一下,如果是一个完全二叉树的话,当空节点从队列里面出来之后,队列剩下的应该是全空,如果后面还有节点,那么肯定说明不是完全二叉树
// 判断二叉树是否是完全二叉树
bool BTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
//如果发现该队列头是空的话,那么直接break
if (front == NULL)
break;
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
//接下来看看队列里面是不是全是空节点
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
// 空后面出到非空,那么说明不是完全二叉树
if (front)
{
QueueDestory(&q);
return false;
}
}
QueueDestory(&q);
return true;
}
3. 缮甲厉兵
1.给以下两种顺序可不可以由此来建一个唯一的树
顺序1 | 顺序2 | 可以/不可以建树 |
---|---|---|
前序 | 中序 | 可以 |
后序 | 中序 | 可以 |
前序 | 后序 | 不可以 |
2. 一棵非空的二叉树的先序遍历序列与后序遍历序列正好相反,则该二叉树一定满足( )
-
所有的结点均无左孩子
-
所有的结点均无右孩子
-
只有一个叶子结点
-
至多只有一个结点
3.如果一颗二叉树的前序遍历的结果是ABCD,则满足条件的不同的二叉树有( )种
- 13
- 14
- 15
- 16
4. 已知某二叉树的前序遍历序列为5 7 4 9 6 2 1,中序遍历序列为4 7 5 6 9 1 2,则其后序遍历序列为( )
-
4 2 5 7 6 9 1
-
4 2 7 5 6 9 1
-
4 7 6 1 2 9 5
-
4 7 2 9 5 6 1
选择题做完了,再做几道编程题
入门二叉树-一起来递归【下】
入门二叉树-一起来递归【上】
最后给到我的代码仓库,有关代码已经放入仓库https://gitee.com/allen9012/c-language/tree/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/BinaryTree3