文章中其实有很多图来帮助理解,但是因为外链的原因,我电脑上的图不能直接拉过来,要完整版的可以评论我直接发PDF版本。个人笔记,仅供参考。
·结点之间的路径只能从上往下
·结点的度:结点的分支数
·树的度:树中各节点的度的最大值
1.满二叉树 2.完全二叉树 3.二叉排序树 4.平衡二叉树
常考考点:
#define MaxSize 100
struct TreeNode{
ElemType value; //结点中的数据元素
bool isEmpty; //结点是否为空
};
TreeNode t[MaxSize];
//初始化
for (int i=0; i<MaxSize; i++){
t[i].isEmpty = true;
}
对于完全二叉树:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9xzA6fog-1585843866454)(/Users/zyairelu/Library/Application%20Support/typora-user-images/Screen%20Shot%202020-04-01%20at%207.00.09%20PM.png)]
struct ElemType{
int value;
};
typedef struct BiTNode{
ElemType data; //数据域
struct BiTNode *lchild, *rchild; //左、右孩子指针
struct BiTNode *parent; //父结点指针
}BiTNode, *BiTree;
//定义一棵空树
BiTree root = NULL;
//插入根结点
root = (BiTree)malloc(sizeof(BiTNode));
root -> data = {1};
root -> lchild = NULL;
root -> rchild = NULL;
//插入新结点
BiTNode * p = (BiTNode *)malloc(sizeof(BiTNode));
p -> data = {2};
p -> lchild = NULL;
p -> rchild = NULL;
root -> lchild = p; //作为根结点的左孩子
· n个结点的二叉链表共有n+1个空链域
算法思想:
· 初始化一个辅助队列;
·根结点入队;
·若队列非空,则队头结点出队,访问该结点,并将其左、右孩子插入队尾(如果有的话)
·重复第三步直至队列为空
//中序遍历
void FindPre(BiTree T){
if (T != NULL){
FindPre(T -> lchild); //递归遍历左子树
visit(T); //访问根结点
FindPre(T -> rchild); //递归遍历右子树
}
}
//访问结点q
void visit(BiTNode *q){
if (q == p) //当前访问结点刚好是结点p
final = pre; //找到p的前驱
else
pre = q; //pre指向点钱访问的结点
}
//辅助全局变量,用于查找结点p的前驱
BiTNode *p; //p指向目标结点
BiTNode *pre = NULL; //指向当前访问结点的前驱
BiTNode *final = NULL; //用于记录最终结果
//全局变量pre,指向当前访问结点的前驱
ThreadNode *pre = NULL;
//中序线索化二叉树
void CreateInThread(ThreadTree T){
pre = NULL; //pre初始为NULL
if (T != NULL){ //非空二叉树才能线索化
InThread(T); //中序线索化二叉树
if (pre -> rchild == NULL)
pre -> rtag = 1; //处理遍历的最后一个结点
}
}
//线索二叉树结点
typedef struct ThreadNode{
ElemType data; //数据域
struct BiTNode *lchild, *rchild; //左、右孩子指针
int ltag, rtag; //左、右线索标志
}ThreadNode, *ThreadTree;
//中序遍历二叉树,一边遍历一边线索化
void InThread(ThreadTree T){
if (T != NULL){
InThread(T -> lchild);
visit(T);
InThread(T -> rchild);
}
}
void visit(ThreadNode *q){
if (q -> lchild == NULL){
q -> lchild = pre;
q -> ltag = 1;
}
if (pre != NULL && pre -> rchild == NULL){
pre -> rchild = q;
pre -> rtag = 1;
}
pre = q;
}
//全局变量pre,指向当前访问结点的前驱
ThreadNode *pre = NULL;
//中序线索化二叉树
void CreateInThread(ThreadTree T){
pre = NULL; //pre初始为NULL
if (T != NULL){ //非空二叉树才能线索化
InThread(T); //中序线索化二叉树
if (pre -> rchild == NULL)
pre -> rtag = 1; //处理遍历的最后一个结点
}
}
//线索二叉树结点
typedef struct ThreadNode{
ElemType data; //数据域
struct BiTNode *lchild, *rchild; //左、右孩子指针
int ltag, rtag; //左、右线索标志
}ThreadNode, *ThreadTree;
//先序遍历二叉树,一边遍历一边线索化
void InThread(ThreadTree T){
if (T != NULL){
visit(T);
if (T -> ltag == 0) //important! 判断lchild不是前驱线索
InThread(T -> lchild);
InThread(T -> rchild);
}
}
void visit(ThreadNode *q){
if (q -> lchild == NULL){
q -> lchild = pre;
q -> ltag = 1;
}
if (pre != NULL && pre -> rchild == NULL){
pre -> rchild = q;
pre -> rtag = 1;
}
pre = q;
}
同上理
//找到以p为根的子树中,第一个被中序遍历的结点
ThreadNode *Firstnode(ThreadNode *p){
//循环找到最左下结点(不一定是叶结点)
while(p->ltag == 0) p = p->lchild;
return p;
}
//在中序线索二叉树中找到结点p的后继结点
ThreadNode *Nextnode(ThreadNode *p){
//右子树中最左下结点
if (p->rtag == 0) return Firstnode(p->rchild);
else return p->rchild; //rtag == 1直接返回后继线索,即是中序后继
}
//对中序线索二叉树进行中序遍历 (利用线索实现的非递归算法 空间复杂度O(1))
void Inorder(ThreadNode *T){
for(ThreadNode *p = Firstnode(T); p != NULL; p = Nextnode(p))
visit(p);
}
//找到以p为根的子树中,最后一个被中序遍历的结点
ThreadNode *Lastnode(ThreadNode *p){
//循环找到最左下结点(不一定是叶结点)
while(p->rtag == 0) p = p->rchild;
return p;
}
//在中序线索二叉树中找到结点p的前驱结点
ThreadNode *Prenode(ThreadNode *p){
//左子树中最右下结点
if (p->ltag == 0) return Lastnode(p->lchild);
else return p->lchild; //rtag == 1直接返回前驱线索,即是中序前驱
}
//对中序线索二叉树进行逆向中序遍历 (利用线索实现的非递归算法 空间复杂度O(1))
void Inorder(ThreadNode *T){
for(ThreadNode *p = Lastnode(T); p != NULL; p = Prenode(p))
visit(p);
}
//在先序线索二叉树中找到结点p的后继结点
ThreadNode *Nextnode(ThreadNode *p){
//若p有左孩子,则先序后继为左孩子,否则为右孩子
//若均无,则为它自己,表明为最后一个结点
if (p->lchild != 0) return p->lchild;
else if (p->rchild != 0) return p->rchild;
else return p;
}
//对先序线索二叉树进行中序遍历,根结点为起始
void Inorder(ThreadNode *T){
for(ThreadNode *p = root; p != NULL; p = Nextnode(p))
visit(p);
}
只有知道p的父结点,才可以找到先序前驱。
后序线索二叉树与先序线索二叉树找前驱后继相似。
#define MAX_TREE_SIZE 100 //树中最多结点
typedef struct{ //树的结点定义
ElemType data; //数据元素
int parent; //双亲位置域
}PTNode;
typedef struct{ //树的类型定义
PTNode nodes[MAX_TREE_SIZE]; //双亲表示
int n; //结点数
}PTree;
struct CTnode{
int child; //孩子结点在数组中的位置
struct CTNode *next; //下一个孩子
};
typedef struct{
CTBox nodes[MAX_TREE_SIZE];
int n, r; //结点数和根的位置
}CTree;
typedef struct CSNode{
ElemType data; //数据域
struct CSNode *firstchild, *nextsibling; //第一个孩子和右兄弟指针
}CSNode, *CSTree;
用队列实现:
树的后根遍历序列与这棵树相应的二叉树的中序序列相同。
左子树结点值 < 根结点值 < 右子树结点值
=> 用中序遍历后,可以得到一个递增的有序序列
//二叉排序树结点
typeof struct BSTNode{
int key;
struct BSTNode *lchild, *rchild;
}BSTNode, BSTree;
//在二叉排序树中查找值为key的结点
BSTNode *BST_Search(BSTree T, int key){
while(T != NULL && key != T->key){ //若树空或等于key的值,则结束
if (key < T->key) T = T->lchild; //小于,则在左子树上查找
else T = T->rchild; //大于,则在右子树上查找
}
return T;
}
BSTNode *BSTSearch(BSTree T, int key){
if (T == NULL)
return NULL; //查找失败
if (key == T->key)
return T; //查找成功,返回结点T
else if (key < T->key)
return BSTSearch(T->lchild. key); //在左子树中查找
else
return BSTSearch(T->rchild, key); //在右子树中查找
}
BSTNode *BST_Insert(BSTree &T, int key){
while(T->key != NULL){
if (T->key == key) //已经存在了该值,则插入失败
return 0;
else if (T->key < key) //左子树
T = T->lchild;
else
T = T->rchild;
}
T->key = key;
return 1; //插入成功
}
递归实现,最坏空间复杂度为O(h),h为深度。
int BST_Insert(BSTree &T, int k){
if (T == NULL){ //原树为空,新插入的结点即为根结点
T = (BSTree)malloc(sizeof(BSTNode));
T->key = k;
T->lchild = NULL;
T->rchild = NULL;
return 1; //返回1,则插入成功
}
else if (k == T->key) //原树中已经存在了相应的值,则插入失败
return 0;
else if (k < T->key) //插入到左子树
return BST_Insert(T->lchild, k);
else //插入到右子树
return BST_Insert(T->rchild, k);
}
//按照 str[]中关键字序列建立二叉排序树
void Creat_BST(BSTree &T, int str[], int n){
T = NULL; //初始时T为空树
int i = 0;
while (i < n){
BST_Insert(T, str[i]);
i++;
}
}
//举个例子
void main(){
str = [13, 15, 8, 5, 9, 12];
int n = sizeof(str);
BSTree T;
Creat_BST(&T, str, n); //over
}
值得注意的是,具有相同元素的str,因素顺序的不同排列可能会产生相同或不相同的二叉排序树。
查找成功的平均查找长度 ASL(Average Search Length)
A S L = 每 个 结 点 查 找 长 度 之 和 结 点 总 数 ASL = \frac{每个结点查找长度之和}{结点总数} ASL=结点总数每个结点查找长度之和
查找失败的平均查找长度 ASL(Average Search Length)
A S L = 每 个 空 叶 结 点 查 找 长 度 之 和 空 叶 结 点 总 数 ASL = \frac{每个空叶结点查找长度之和}{空叶结点总数} ASL=空叶结点总数每个空叶结点查找长度之和
平衡二叉树(Balanced Binary Tree),简称平衡树(AVL树)——树上任一结点的左子树和右子树的高度之差不超过1。
结点的平衡因子 = 左子树高 - 右子树高
故可以定义平衡二叉树结点:
//平衡二叉树结点
typedef struct AVLNode{
int key; //数据域
int balance; //平衡因子
struct AVLNode *lchild, *rchild;
}AVLNode, *AVLTree;
总结来看,就是调整最小不平衡子树,则其他的子树包括总的树都会平衡。
平衡二叉树——树上任一结点的左子树和右子树的高度之差不超过1.
假设以 n h n_h nh表示深度为 h h h的平衡树中含有的最少结点数,
则有$n_0 = 0, n_1 = 2, $ 并且有 n h = n h − 1 + n h − 2 + 1 n_h = n_{h-1} + n_{h-2} + 1 nh=nh−1+nh−2+1.
所以含有 n n n个结点的平衡二叉树的最大深度为 O ( log 2 n ) O(\log_2n) O(log2n),平衡二叉树的平均查找长度为 O ( log 2 n ) O(\log_2n) O(log2n)。
· 结点的权:有某种现实含义的数值(如:表示结点的重要性等)
· 结点的带权路径长度:从树的根到该结点的路径长度(经过的边数)与该结点上权值的乘积
· 树的带权路径长度:树中所有叶结点的带权路径长度之和(WPL, Weighted Path Length)
W P L = ∑ i = 1 n w i l i WPL = \sum_{i=1}^n{w_il_i} WPL=i=1∑nwili
在含有 n n n个带权叶结点的二叉树中,其中带权路径长度(WPL)最小的二叉树称为哈夫曼树,也称最优二叉树。
给定 n n n个权值分别为 w 1 , w 2 , . . . , w n w_1, w_2,...,w_n w1,w2,...,wn的结点,构造哈夫曼树的算法描述如下: