内容来自浙大数据结构MOOC 本文仅为学习笔记
一、基本概念
- 树的度:所有结点的度中最大的度
- 结点的层次:规定根结点在第1层
二、树的表示
1.表示方法的困难之处
(1)由于每个结点的father和child个数不统一,在后续程序处理中出现麻烦
(2)如果尝试用一个统一的方法来表示所有的结点,
如:可以用child个数最大的结点的结构来表示所有的结点。
问题:会造成空间浪费
2. 改进的一种表示方式:child-sibling表示法
(1)对于每个Element: FirstChild指向第一个儿子,NextSibling指向下一个兄弟
(2)虽然有空间浪费,但空间浪费减少
(3)对于每个结点,都有两个分叉(左/右),一个为FirstChild,另一个为NextSibling,即为二叉树
三、二叉树基本理念
1. 基本概念
(1)与普通的度为2的树的区别:二叉树的左右分叉有区别
(2) 特殊二叉树
- 斜二叉树(Skewed Binary Tree):每个结点都只有同一侧的分叉,构成了一个链表
- 完美/满二叉树(Perfect/Full Binary Tree):所有的结点都有左和右
- 完全二叉树(Complete Binary Tree): 结点的编号与满二叉树(从上至下、从左到右的顺序的)编号相同。即相对满二叉树来说,最后一层的树可以从右侧缺少一些。
2. 重要性质
(1)一个二叉树的第i层最多结点数为2^(i-1),i>=1
(2)深度为k的二叉树的最大结点总数为:2^k-1,k>=1
(3)对任何非空二叉树,n_0=叶结点(没有儿子)的个数,n_2=有2个儿子的结点的个数,n_0=(n_2) +1
*证明:从边和点的个数的关系出发
3. 二叉树的基本操作
(1)Boolean IsEmpty(BinTree BT)
判断二叉树是否为空
(2)void Traversal(BinTree BT)
遍历,按某顺序访问每个结点
常用遍历方法:
- void PreOrderTraversal(BinTree BT):先序,根-左子树-右子树
- void InOrderTraversal(BinTree BT):中序,左子树-根-右子树
- void PostOrderTraversal(BinTree BT):后序,左子树-右子树-根
- void LevelOrderTraversal(BinTree BT):层次遍历,从上到下,从左到右
(3) BinTree CreatBinTree()
创造一个二叉树
四、二叉树的存储方式
1. 顺序存储结构
用数组,从上至下、从左到右的顺序存储(从1开始)
对于完全二叉树而言:
特点:
(1)对于任何非根结点,它的父结点序号为:floor(i/2)
(2)结点i的左孩子结点序号为2i,右孩子的结点序号为2i+1
对于一般的二叉树:
补充为一个完全二叉树。留下空缺。会造成空间浪费。
2. 链表存储
typedef struct TreeNode* BinTree;
typedef BinTree Position;
struct TreeNode{
ElementType Data;
BinTree Left;
BinTree Right;
}
五、二叉树的遍历
核心问题:将二叉树这种二维结构线性化
1. 先序遍历
根-左-右
void PreOrderTraversal(BinTree BT){
if(BT){ //to check if the tree is empty
printf("%d",BT->Data);
PreOrderTraversal(BT->Left);
PreOrderTraversal(BT->Right);
}
}
2. 中序遍历
左-根-右
void InOrderTraversal(BinTree BT){
if(BT){ //to check if the tree is empty
PreOrderTraversal(BT->Left);
printf("%d",BT->Data);
PreOrderTraversal(BT->Right);
}
}
3. 后序遍历
左-右-根
void PostOrderTraversal(BinTree BT){
if(BT){ //to check if the tree is empty
PreOrderTraversal(BT->Left);
PreOrderTraversal(BT->Right);
printf("%d",BT->Data);
}
}
总结:以上三种结点走的路径是一样的,都是从入口开始往左-根-右-根;
只是访问各结点的时机不同。
*堆栈而不用递归的方法
- 中序遍历
实现思路:
- 遇见一个结点,就把它压栈,并去遍历它的左子树
- 左子树遍历结束后,从栈顶弹出这个结点并访问它
- 然后按其右指针再去中序遍历该结点的右子树
void InOrderTraversal(BinTree BT){
BinTree T=BT; //not to change BT
Stack S=CreatStack(MaxSize); //initialize stack S
while(T || IsEmpty(S){ //T and stack are not empty
while(T){ // push the left tree into the stack
Push(S,T);
T=T->Left;
}
if(!IsEmpty(S){
T=Pop(S); // pop the element if all its left tree has been pushed into the stack
printf("%5d",T->Data);
T=T->Right; // turn to the right tree
}
}
}
-先序遍历&后序遍历
从前面的总结来看,先、中、后的走过的路径是一样的,故仅需在中序遍历的基础上稍加调整顺序即可
4. 层次遍历
队列实现:
遍历从根结点开始,根结点入队,开始执行循环:
结点出队,访问该结点,左右儿子入队
void LevelOrderTraversal(BinTree BT){
Queue Q; BinTree T;
if (!BT) return; //If it tree is empty, return;
Q=CreatQueue(MaxSize); //create a queue
AddQ(Q,BT);
while(!IsEmptyQ(Q)){
T=DeleteQ(Q);
printf("%d\n",T->Data);
// add the left and right child into the queue
if (T->Left) {AddQ(Q,T->Left);}
if (T->Right) {AddQ(Q,T->Right);}
}
}
五、二叉树的应用
1. 求二叉树的高度
Height =max(HL,HR)+1
后序遍历(左-右-根),递归
int PostOrderGetHeight(BinTree BT){
int HL,HR,MaxH;
if (BT){
HL=PostOrderGetHeight(BT->Left);
// get the height of the left tree
HR=PostOrderGetHeight(BT->Right);
// get the height of the right tree
MaxH=(HL>HR)? HL:HR;
return (MaxH+1);
}
else return 0; //the height of the empty tree is 0
}
2. 二元运算表达式树
叶结点是运算变量,非叶结点为运算符号
三种遍历得到三种不同的访问结果:x序遍历得到x缀表达式
*中缀表达式会受到运算优先级的影响
-> 改进方法:添加括号
3. 由两种遍历序列确定二叉树
必须要有中序遍历才可确定
思路:
- 根据先序遍历第一个结点确定根结点
- 根据结点在中序遍历中分割出左右两个子序列
- 对左右子树分别递归使用相同的方法继续分解