逻辑链条
1.树的定义
1.术语清单
将书中所有的加粗的基本概念摘录出来了,方便自查。
- 子树
- 根
- 度
- 树的度
- 叶子
- 孩子
- 双亲
- 兄弟
- 祖先
- 子孙
- 层次
- 堂兄弟
- 深度/高度:从1开始,从上往下。
- 有序树/无序树
- 森林
2.二叉树
二叉树的左右结点是不能调换的,换了就不是同一棵树了。
性质
二叉树可以视为一种公比为2的等比数列
将《数据结构》中的5个基本性质都简单证明了一遍。
特殊二叉树
- 满二叉树: 高度是h,同时有2^h - 1个结点。(判断:全是结点)
- 完全二叉树: 按照满二叉树进行数,序号和位置可以按照满二叉树位置对上。
二叉树的应用
- 递归版本: 什么序,根在哪。
- 非递归版本:
//非递归先序遍历: 根左右
void PreOrder(BiTree T){
stack<BiTree> s;
BiTree p = T;
while(p != NULL ||s.empty() == false){//当p的值不为空的时候 或者 栈不空的时候
while(p != NULL){
printf("%c " , p->data);
s.push(p);
p = p->lchild;
}
if(s.empty() == false){
s.pop();
p = p->rchild;
}
}
}
中序遍历:
void InOrder(BiTree T){
stack<BiTree> s;
BiTree p = T;
while(p != NULL || s.empty() == false){
s.push(p);
p = p->lchild;//这个循环里面一路找左结点//
}
//出来的时候一定是没有左结点的//
p = s.top();//此时的栈顶必定是这个左结点的中结点//
s.pop();
printf("%c " , p->data);
p = p->rchild;
}
后序遍历:
void PostOrder(BiTree T){
stack<BiTree> s;
BiTree p , r = NULL;//r的意思是辅助指针,
while(p != NULL || s.empty() == false){
if(p != NULL){
s.push(p);
p = p->lchild;//这个地方和中序遍历一样,一开始直接找最左边的左结点//
}
else{
p = s.top();//指针指回栈顶,当前栈顶是最后的左下角的左指针的中节点
if(p->rchild != NULL && p->rchild != r)//右结点不是空的并且尚未访问过
p = p->rchild;//那么指向这个右结点
else{//说明没有右结点,或者是右结点已经访问过了
s.pop();//弹出这个中结点
printf("%c" , p->data);
r = p;//r指向访问过的右子树根节点//
p = NULL;//到这一步的时候,变成NULL是为了走==NULL分支再给p赋值//
}
}
}
}
层序遍历:
void LevelOrder(BiTree T){
queue<BiTree> q;
BiTree p;
q.push(T);//头节点放进去//
while(q.empty() == false){
p = q.front();
q.pop();
printf("%c " , p->data);
if(p->lchild != NULL){
q.push(p->lchild);
}
if(p->rchild != NULL){
q.push(p->rchild);
}
}
}
线索二叉树:
ltag | lchild | data | rchild | rtag |
---|
1.左子树为空,指向其前驱;右子树为空,指向其后继。先写出来它的序列,然后串起来即可。
2.既可橡树一样遍历,也可以通过指针直接遍历;但是在线索化之后,后序线索二叉树也会出现问题,需要栈来去辅助,原因是当后序遍历是左右根,导致右结点访问完之后往回找的时候因为已经占位指向孩子的右结点了,无法在指向父结点了。
树&森林
1.树的表示
双亲表示法
#include<iostream>
#define Maxsize 100
using namespace std;
typedef char ElemType;
//树的结点定义
typedef struct TNode{
ElemType data;
int parent;
}TNode;
//树的定义//
typedef struct{
TNode nodes[Maxsize];
int n;//结点的数量//
}Tree;
孩子表示法
#include<iostream>
#define Maxsize 100
using namespace std;
typedef char ElemType;
//孩子结点的定义
typedef struct CNode{
int child; //在表中的数组的下表//
struct CNode *next; //这个是一个结构
}CNode , *Child;
//树的所有数据表
typedef struct{
ElemType data;
Child firstchild; // 指向的第一个孩子的数据 //
}TNode;
//树的定义//
typedef struct{
TNode nodes[Maxsize];
int n;//结点的数量//
}Tree;
孩子兄弟表示法
#include<iostream>
#define Maxsize 100
using namespace std;
typedef char ElemType;
//孩子结点的定义
typedef struct CSNode{
ElemType data;
struct CNode *firstchild , *rightbro; //左手孩子,右手兄弟
}CSNode;
2.树,森林转换
左孩右兄
3.树的遍历
树 | 森林 | 二叉树 |
---|---|---|
先根遍历 | 先序遍历 | 先序遍历 |
后根遍历 | 中序遍历 | 中序遍历 |
应用
1.并查集
并查集用于Kruskal算法,借鉴了树的思想,方便合并同类。
//初始化
void Init(){
for(int i = 0 ; i < n ; i ++)
father[i] = -1;
}
//并
void Union(int a , int b){
if(a!= b){
father[a] = b;
}
}
//查
int Findfather(int father[] , int x){
while(father[x] > 0){
x = S[x];
}
return x;
}
2.Binary Search Tree
左 < 根 < 右,中序遍历可得一个递增序列。
查找操作
BSTNode *BST_Search(BiTree T , ElemType key , BSTNode *&p){
p = NULL;
while(T != NULL && key != T->data){
p = T;
if(key < T->data) T = T->lchild;
else T = T->rchild;
}
return T;
}
插入
int BST_Insert(BiTree &T , KeyType k){
if(T == NULL){ //空树建立新节点
T = (BiTree)malloc(sizeof(BSTNode));
T->key = k;
T->lchild = T->rchild = NULL;
}
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);
}
构造
void Creat_BST(BiTree &T , KeyType str[] , int n){
T = NULL;
int i = 0;
while(i < n){
BST_Insert(T , str[i]);
}
}
删除
此处不涉及代码,只涉及思想。
左空补右,右空补左,左右不空,中序右补。(交换后单树调整。)
查找效率: 可能出现单边树;静态查找表用顺序表,动态查找表,二叉排序树。ASL会求,详见ASL树问题。
3.Balance Binary Tree
平衡因子: |左树高 - 右树高|不超过1。
插入: LL,RR,LR,RL。哪里不平衡,哪里就调整,从下往上。(细节看书,不做深究。)
查找: BST相同。
4.Huffman Tree
WPL最小,即为哈夫曼树。
WPL的求法: 树的WPL = ∑(叶结点权值 * 路径数)。
构造: 池子拿出最小两个点结合(权值相加),结合的结点放回池子里再次操作,得一棵树。权值越小,离根越远;n个结点最后树有2n - 1个结点,(结合一次多一个结点)
编码: 左0右1。可得最短二进制前缀编码,必是最优,但是不一定唯一。