**树与图的代码
树---大题
非空树的特性:
-
有且仅有一个根节点
-
没有后继的结点称为“叶子结点”(终端结点)
-
有后继的结点称为“分支结点”(非终端结点)
-
除了根节点外,任何一个结点都有且仅有一个前驱
-
每个结点可以有0个或多个
**节点的层次:从上往下数
**节点的高度:从下往上数
树的高度:总共有几层
节点的度:有几个分支
树的度:各个结点度的最大值
性质
-
结点数=总度数(边数)+1;
-
度为m的树:
-
任意节点的度<=m;
-
至少有一个节点度=m;
-
一定是非空树,至少有m+1个结点
-
-
m叉树:
-
任意节点的度<=m;
-
允许所有结点的度都<m;
-
可以是空树
-
-
度为m的树第i层至多有m^(i-1)个节点(i>=1)。
-
高度为h的m叉树至多有(m^h-1/m-1)个结点。
-
高度为h的m叉树至少有h个节点
-
高度为h,度为m的树至少有h+m-1个结点
-
具有n个节点的m叉树的最小高度为[logm(n(m-1)+1)]
存储结构
-
双亲表示法(顺序存储):每个结点中保存指向双亲的“指针”。
-
#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{ ElemType data; struct CTNode *firstChild; }CTBox; typedef struct{ CTBox nodes[MAX_TREE_SIZE]; int n,r; }CTree;
-
孩子兄弟表示法(链式存储)
-
typedef struct CSNode{ ElemType data; struct CSNode *firstchild,*nextsibling//二叉链表//第一个孩子和右兄弟指针 }CSNode,*CSTree;
遍历
-
先根遍历
先根遍历序列与这颗树相应二叉树的先序序列相同。
void PreOrder(TreeNode *R){ if(R!=NULL){ visit(R);//访问根节点 while(R还有下一个子树T) PreOrder(T); } }
-
后根遍历
后根遍历序列与这颗树相应二叉树的中序序列相同。
void PostOrder(TreeNode *R){ if(R!=NULL){ while(R还有下一个子树T) PostOrder(T); visit(R);//访问根节点 } }
-
层次遍历(队列实现)
-
若树非空,则根节点入队
-
若队列非空,队头元素出队并访问,同时将该元素的孩子依次入队
-
重复二直到队列为空
-
二叉树
-
n个节点的有限集合或空二叉树
-
由一个根节点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一棵二叉树
-
特点
-
每个结点最多有两棵子树
-
左右子树不能颠倒(有序树)
-
-
五种状态
-
空二叉树
-
只有左子树
-
只有右子树
-
只有根节点
-
左右子树都有
-
-
满二叉树
-
一颗高度为h,且含有2^h-1个节点的二叉树
-
-
完全二叉树
-
有且仅当其每个结点都与高度为h的满二叉树中编号为1-n的结点一一对应时,成为完全二叉树。
-
-
二叉排序树
-
左子树上的所有结点的关键字均小于根节点的关键字
-
右子树上的所有结点的关键字均大于根节点的关键字
-
左子树和右子树又各是一颗二叉排序树
-
-
平衡二叉树
-
树上任一结点的左子树和右子树的深度之差不超过1
-
常考性质
-
设非空二叉树中度为0,1,2的结点数分别为n0,n1,n2,则n0=n2+1;//叶子结点数为度为2的结点数+1
-
假设树中结点总数为n,则
-
n=n0+n1+n2
-
n=n1+2n2+1
-
-
具有n个(n>0)结点的完全二叉树的高度h为[log2(n+1)]或[log2n]+1
-
对于完全二叉树,可以由总结点数n推出度为0,1,2的结点个数
-
若完全二叉树有2k个结点则必有n1=1;n0=k;n2=k-1;
-
若完全二叉树有2k-1个结点则必有n1=0;n0=k;n2=k-1;
-
-
非空二叉树第二层至多有2^(k-1)个节点
存储结构
-
顺序存储
-
#define MaxSize 100 struct TreeNode{ ElemType value; bool isEmpty; } TreeNode t[MaxSize];//按照从上至下,从左至右的顺序一次存储完全二叉树的各个结点 for(int i=0;i<MaxSize;i++){ t[i],isEmpty=true; }//初始化时所有结点标记为空
i的左孩子 --2i
i的右孩子 --2i+1
i的父节点 --[i/2]
i所在的层次 --[log2(n+1)]或[log2n]+1
-
链式存储
-
n个节点的二叉链表共有n+1个空链域
-
struct ElemType{ int value; }; typedef struct BiTNode{ ElemType data; struct BitNode *lchild,*rchild; }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;
-
二叉树的遍历
-
先序遍历:根左右O(h+1)
-
void PreOrder(BiTree T){ if(T!=NULL){ visit(T); PreOrder(T->lchild); PreOrder(T->rchild); } }
-
中序遍历:左根右
-
void InOrder(BiTree T){ if(T!=NULL){ InOrder(T->lchild); visit(T); InOrder(T->rchild); } }
-
后序遍历:左右根
-
void PostOrder(BiTree T){ if(T!=NULL){ PostOrder(T->lchild); PostOrder(T->rchild); visit(T); } }
-
层序遍历
-
思想:
-
初始化一个辅助队列
-
根结点入队
-
若队列非空,则队头结点出队,访问该结点,并将其左右孩子插入队尾
-
重复3直至队列空
-
-
-
void LevelOrder(BiTree T){ LinkQueue Q; InitQueue(Q); BiTree 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->r3child); } } }
-
线索二叉树
前驱线索:由左孩子指针充当;
后继线索:由右孩子指针充当;
存储结构:
typedef struct ThreadNode{ ElemType data; struct ThreadNode *lchild,*rchild; int ltag,rtag;//左右线索标志;tag==0,表示指针指向孩子;tag==1,表示指针是“线索” }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; } ThreadNode *pre=NULL;
二叉树找中序后继
//找到以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,直接返回后继线索 } //在中序线索二叉树进行中序遍历(利用线索实现的非递归算法) void Inorder(ThreadNode *T){ for(ThreadNode *p=Firsrnode(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;//ltag==1,直接返回后继线索 } //在中序线索二叉树进行逆向中序遍历(利用线索实现的非递归算法) void RevInorder(ThreadNode *T){ for(ThreadNode *p=Lasrnode(T);p!=NULL;p=Prenode(p)) visit(p); }
二叉树找先序后继:若有左孩子,则后继为其左孩子,无左孩子后继结点为其右孩子;
二叉树找先序前驱:(p左孩子,有父节点)p的父节点为其前驱;(p右孩子,左兄弟为空)p的父节点为其前驱;(p右孩子,左兄弟非空)p的前驱为左兄弟子树中最后一个被先序遍历的结点;(p根节点)无前驱;
二叉树找后序后继:(p右孩子,有父节点)p的父节点为其后继;(p左孩子,右兄弟为空,有父节点)p的父节点为其后继;(p左孩子,右兄弟非空,有父节点)右兄弟子树中第一个被后序遍历的结点;(p根节点)没有后继
二叉树找后序前驱:(有右孩子)右孩子为其前驱;(无右孩子)左孩子为其前驱;
有序树
树中节点的各子树从左到右是有次序的,不能互换。
无序树
树中节点的各子树从左到右是无次序的,可以互换。
森林
m棵互不相交的树的集合。
遍历
-
先序遍历:
-
若森林非空,则按如下规则进行遍历:
-
访问森林中第一棵树的根节点。
-
先序遍历第一棵树中根节点的子树森林。
-
先序遍历除去第一棵树之后剩余的树构成的森林
等同于依次对二叉树的先序遍历
-
-
中序遍历
-
若森林非空,则按如下规则进行遍历:
-
中序遍历森林中的第一棵树的根节点的子树森林。
-
访问第一棵树的根节点
-
中序遍历除去第一棵树之后剩余的树构成的森林。
等同于依次对二叉树的中序遍历
-
哈夫曼树
带权路径长度:
结点的权:有某种显示含义的数值。
树的带权路径长度WPL:书中所有叶子结点的带权路径长度之和
1)每个初始结点最终都成为叶结点,且全职越小的结点到根结点的路径长度越大
2)哈夫曼树的结点总数2n-1
3)哈夫曼树中不存在度为1的结点
4)哈夫曼树并不唯一,但WPL必然相同且为最优
哈夫曼编码
固定长度编码--每个字符用相等长度的二进制位表示
可变长度编码--允许对不同字符用不等长的二进制位表示
若没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码
由哈夫曼树得到哈夫曼编码--字符集中的每个字符作为一个叶子结点,各个字符出现的频度作为结点的权值,根据之前介绍的方法构造哈夫曼树
并查集、
Find--"查"操作:确定一个指定元素所属集合;
union--”并“操作:将两个不相交的集合合并为一个
注:并查集是逻辑结构--集合的一种具体实现,只进行”并“和”查“两种基本操作
#define SIZE 13 int UFSets[SIZE]; //集合元素数组 //初始化并查集 void Initial(int S[]){ for(int i=0;i<SIZE;i++) S[i]=-1; } //Find"查"操作,找x所属的集合(返回x所属根结点)O(n)/O(log2n) int Find(int S[],int x){ while(S[x]>=0) //循环寻找x的根 x=S[x]; return x; //根的s[]小于 } //Union"并"操作,将两个集合合并为一个O(1) void Union(int S[],int Root1,int Root2){ //要求Root1与Root2是不同的集合 if(Root1=Root2) return; //将根Root2连接到另一根Root1下面 S[Root2]=Root1; } //优化思路: //1)用根节点的绝对值表示树的结点总数 //2)Union操作,让小树合并到大树 //Union"并"操作,小树合并到大树 //该方法构造的树高不超过[log2n]+1//终极:O(na(n)) void Union(int S[],int Root1,int Root2){ if(Root1==Root2) return; if(S[Root2]>S[Root1]){ //Root2结点数更少 S[Root1]+= S[Root2];//累加结点总数 S[Root2]=Root1;//小树合并到大树 }else{ S[Root2]+=S[Root1]; S[Root1]=Root2; } } //终极优化Find(压缩路径)O(a(n)) int Find(int S[],int x){ int root = x; while(S[root]>=0) root = S[root];//循环找到根 while(x!=root){ //循环寻找x的根 int t=S[x]; S[x] = root; x=t; } return root; //返回根节点编号 }