3.1.1 树的孩子兄弟结点表示法
firstchild是指向该节点第一个儿子节点的指针,nextbro是指向下一个兄弟节点的指针
typedef struct TNode{
Elemtype data; //每个节点的权值
struct TNode *firstchild, *nextbro;
}TNode;
3.1.2 树的双亲表示法
树和森林(节点用双亲表示法存储,常用于并查集)
一般采用数组表示,data数组保存每个节点的权值,father数组保存每个节点的父亲节点在数组中的下标:
//顺序存储(一般使用)
#define MAXSIZE 100
typedef struct PNode{
int value; //该结点的值
int parent; //伪指针,用于指向双亲结点(双亲结点的数组下标)
}PNode;
typedef struct PTree{
PNode node[MAXSIZE]; //申明一片PNode类型个数为MAXSIZE的数组用于存储树
int n; //表示该树中的结点个数
}PTree;
//链式存储
typedef struct TNode{
Elemtype data; //每个节点的权值
struct TNode *father; //father是指向该节点父亲节点的指针
}TNode; //树节点
3.1.3 二叉树的左右孩子结点表示法
typedef struct BTNode{
Elemtype data; //每个节点的权值
struct BTNode *lchild,*rchild; //指向左右孩子节点的指针
}BTNode, *BiTree; //二叉树节点
3.3.1 二叉树的递归遍历(先序、中序、后序)
//前序遍历
void PreOrder(BiTree T){
if(T==NULL) return;
visit(T);
PreOrder(T->lchild);
PreOrder(T->rchild);
}
//中序遍历
void InOrder(BiTree T){
if(T==NULL) return;
InOrder(T->lchild);
visit(T);
InOrder(T->rchild);
}
//后序遍历
void PostOrder(BiTree T){
if(T==NULL) return;
PostOrder(T->lchild);
PostOrder(T->rchild);
visit(T);
}
3.3.2 树(二叉树)的层次遍历
//层序遍历
void LevelOrder(BiTree T){
InitQueue(Q);
BTNode *p;
EnQueue(Q,T); //根节点入队
while(!IsEmpty(Q)){
DeQueue(Q,p); //队头元素出队
visit(p);
if(p->lchild!=NULL) EnQueue(Q,p->lchild);
if(p->rchild!=NULL) EnQueue(Q,p->rchild);
}
}
3.3.3 完全二叉树、二叉排序树、平衡二叉树、哈夫曼树的定义和判定
1. 完全二叉树
1)当且仅当其每个结点都与高度为 h 的二叉树中编号为1 - n 的节点一一对应
2)只有可能有一个度为1的叶子结点(在倒数第二层)
3)叶子结点出现在最后两层
4)按层序编号,根节点为1,则结点 i 的左孩子为2i,右孩子为2i + 1,父结点为⌊ i / 2⌋
5)i ≤ ⌊n/2⌋为分支节点 i > ⌊n/2⌋为叶子结点
2. 二叉排序树
1)左子树结点值 < 根结点值 < 右子树结点值,且它的左右子树又递归的满足这一特性
2)对二叉排序树进行中序遍历,得到的序列是递增的有序序列
typedef struct BSTNode{
elemtype data;
struct BSTNode *rchild, *lchild;
}BSTNode, *BSTree;
3. 平衡二叉树
1)树上任意一个结点的左右子树的深度差不超过1
2)平衡二叉树具有更高的搜索效率
4. 哈夫曼树
1)哈夫曼树:n个结点形成的所有二叉树中,wpl值最低的树(也称为最优二叉树)
2)树的带权路径长度(WPL):所有叶结点的带权路径长度之和
3.3.4 二叉树的非递归遍历
//中序遍历的非递归实现
void InOreder2(BiTree T){
InitSatck(S); BTNode *p=T;
while(p||!IsEmpty(S)){
if(p){
Push(S,p);
p=p->lchild;
}
else{
Pop(S,p); visit(p);
p=p->rchild;
}
}
}
//前序遍历的非递归实现
void PreOrder2(BiTree T){
InitStack(S); BTNode *p=T;
while(p||!IsEmpty(S)){
if(p){
visit(p);
Push(S,p);
p=p->lchild;
}
else{
Pop(S,p);
p=p->rchild;
}
}
}
//后序遍历的非递归实现(也可在节点中增加一个标志域,记录是否已被访问)
void PostOrder2(BiTree T){
InitStack(S); BTNode *p=T,r=NULL;
while(p||!IsEmpty(S)){
if(p){
Push(S,p);
p=p->lchild;
}
else{
GetTop(S,p);
if(p->rchild && p->rchild!=r) p=p->rchild;
else{
pop(S,p); visit(p->data);
r=p; p=NULL;
}
}
}
}
3.3.5 线索二叉树的遍历
//求中序线索二叉树中中序遍历下的第一个节点
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;
}
//中序线索二叉树的中序遍历
void InOrder(ThreadNode *T){
for(ThreadNode *p=Firstnode(T);p!=NULL;p=Nextnode(p)) visit(p);
}
3.4.1 P149.8 计算二叉树中双分支结点的个数
int num=0;
void CountDegreeTwo(BiTree T){
if(T==NULL) return;
if(T->lchild!=NULL&&T->rchild!=NULL) num++;
InOrder(T->lchild);
InOrder(T->rchild);
}
3.4.2 P149.9 交换二叉树中所有左右子树
void ChangeLeftRight(BiTree T){
if(T){
ChangeLeftRight(T->lchild);
ChangeLeftRight(T->rchild);
temp=T->lchild;
T->lchild=T->rchild;
T->rchild=temp;
}
}
3.4.3 P149.10 求先序遍历第k个元素
int num=0;
int PreOrder(BiTree T,int k){
if(T==NULL) return -11111; //若为空节点,返回特殊值
num++;
if(k==num) return T->data;
PreOrder(T->lchild);
PreOrder(T->rchild);
}
3.4.4 P149.11 删去值为x的子树
//前序遍历(注意要判断的是左孩子和右孩子是否等于x,这样才能删除)
void PreOrder(BiTree T,int x){
if(T==NULL) return;
if(T->lchild){
if(T->lchild->data==x){
DeleteXTree(T->lchild);
T->lchild=NULL;
}
}
if(T->rchild){
if(T->rchild->data==x){
DeleteXTree(T->rchild);
T->rchild=NULL;
}
}
PreOrder(T->lchild);
PreOrder(T->rchild);
}
3.4.5 P150.13 查找二叉树中两个结点的公共祖先结点
后序遍历最后访问根结点,即在递归算法中,根是压在栈底的。本题要找p和q的最近公共祖先结点r,不失一般性, 设p在q的左边。
算法思想:**采用后序非递归算法,栈中存放二叉树结点的指针,当访问到某结点时,栈中所有元素均为该结点的祖先。**后序遍历必然先遍历到结点p,栈中元素均为P的祖先。先将栈复制到另一辅助栈中。 继续遍历到结点q时,将栈中元素从栈顶开始逐个到辅助栈中去匹配,第一个匹配(即相等)的元素就是结点P和q的最近公共祖先。
↓顺序存储,编号分别为p和q的节点的公共祖先节点
#define MAXSIZE 100
typedef struct treeNode{
elemType value;
bool isEmpty;
}treeNode;
treeNode T[MAXSIZE];
elemType commonParent(treeNode T, int p, int q){
//寻找公共结点
while (p != q){
//谁大谁找双亲结点
if (p > q) p /= 2;
else q /= 2;
}
return T[p].value;
}
3.4.6 P150.14 求二叉树的宽度
采用层次遍历的方法求出所有节点的层次,并将所有节点和对应的层次放在一个队列中,然后通过扫描队列求出各层的节点总数。
int MaxWidth(BiTree bt){
if(bt==NULL) return 0;
BiTree Q[]; BTNode *p;
front=1; rear=1; //队头,队尾
last=1; //同层最右节点在队列中的位置
tmpe=0; maxw=0; //局部宽度,最大宽度
Q[rear]=bt; //根节点入队
while(front<=last){
p=Q[front++]; temp++;
if(p->lchild!=NULL) Q[++rear]=p->lchild;
if(p->rchild!=NULL) Q[++rear]=p->rchild;
if(front>last){ //一层结束
last=rear;
if(temp>maxw) maxw=temp; //更新最大宽度
temp=0;
}
}
return maxw;
}
3.5 并查集的应用
3.5.1 写代码:定义一个并查集(用长度为n的数组实现)
#define SIZE 13
int UFSets[SIZE];
//初始化并查集
void Initial(int S[]){
for(int i=0;i<SIZE;i++){
S[i]=-1;
}
}
3.5.2 基于上述定义,实现并查集的基本操作—— 并 Union
//Union 并操作,将两个集合合并为一个
void Union(int S[], int Root1, int Root2){
if(Root1==Root2) return; //要求两个根属于不同的集合(不是同一棵树)
S[Root2]=Root1; //Root2的父节点是Root1
} //时间复杂度为O(1)
3.5.3 基于上述定义,实现并查集的基本操作—— 查 Find
//Find 查操作,找x所属集合(返回x所属根节点的下标)
int Find(int S[], int x){
while(S[x]>=0) //循环寻找根
x=S[x];
return x; //最坏时间复杂度为O(n)
}
3.5.4 并查集的优化(Union操作优化——小树合并到大树)
优化思路:在每次Union操作构建树的时候,尽可能让树不长高
- 用根节点的绝对值表示树的结点总数
- Union操作:让小树合并到大树
//Union优化:小树合并到大树
void Union(int S[], int Root1, int Root2){
if(Root1==Root2) return;
if(S[Root2]>S[Root1]){
//S[Root1]小,说明Root1所连结点较多,树高,应让Root2的父节点设置为Root1
S[Root1]+=S[Root2]; //累加结点总数
S[Root2]=Root1; //小树合并到大树
}
else{
S[Root2]+=S[Root1];
S[Root1]=Root2;
}
}
Union操作优化后Find操作最坏时间复杂度变为O(logn)
(该方法构造的树高不超过[logn]+1) []向下取整
3.5.5 并查集的优化(Find操作优化——压缩路径)
优化思路:先找到根节点,再将查找路径上的所有结点都挂到根节点下(从下往上挂节点)
int Find(int S[], int x){
int root=x;
while(S[root]>=0) root=S[root]; //循环找到根
while(x!=root){
int t=S[x]; //t指向x的父节点 (t保存x的爸爸)
S[x]=root; //x直接挂到根节点下 (x的爸爸变为根root)
x=t; // (x更新为自己的爸爸t,继续循环)
}
return root; //返回根节点编号
}
压缩路径后树高不会超过O(α(n)) 可使时间开销降低