数据结构(第五章)
数据结构(第五章)
树
- 定义:树是n(n>=0)个结点的有限集。n=0是称为空树。在任意一颗非空树中:1. 有且仅有一个特定的称为根的结点 。 2.当n>1时,其余结点可分为m个互不相交的有限集,其中每一个集合本身又是一颗树,并且称为根的子树。
- 特点:树是一种递归的数据结构。树的根结点没有前驱,除根结点外的所有结点有且仅有一个前驱。 树中所有结点可以有零个或多个后继。
基本特性
- 从根A到结点K的唯一路径上的任意结点,称为K的祖先。
- 树中的一个结点的孩子个数称为该结点的度,树中结点的最大度数称为树的度。
- 度大于0 的结点称为分支结点(终端结点),度为0的结点称为叶子结点。
- 结点的深度是从根结点开始自顶向下逐层累加
- 结点的高度是从叶子结点开始自底向上逐层累加。
- 树的高度(或深度)是树中结点的最大层数。
- 有序树和无序树。树中结点的各子树从左到右是有次序的,不能互换,称该树为有序树,否则称为无序树。
- 路径和路径长度。树中两个结点之间的路径是由这两个结点之间所经过的结点序列构成的,而路径长度是路径上所经过的边的个数。
- 森林是m(m>=0)棵互不相交的树的集合。
基本性质
- 树中的结点数等于所有结点的度数之和加1.
- 度为m的树中第i层上至多有m^(i-1)个结点(i>=1).
- 高度为h的m叉树至多有(m^h-1)/(m-1)个结点。
- 具有n个结点的m叉树的最小高度为[logm(n(m-1)+1)] --向上取整
- 高度为h的m叉树至少有h个结点
- 高度为h,度为m的树至少有h+m-1个结点
二叉树
- 定义:每个结点至多只有两棵子树,并且二叉树的子树有左右之分,其次序不能任意颠倒
几种特殊的二叉树
- 满二叉树
一棵高度为h,且含有2^h-1个结点的二叉树称为满二叉树,即树种的每层都含有最多的结点。满二叉树的叶子结点都集中在二叉树的最下一层,并且除叶子结点外的每个结点的度都为2.
若有双亲则双亲为i/2 , 左孩子为 2i 右孩子为2i+1
- 完全二叉树
高度为h、有n个结点的子树,当且仅当其每个结点都与高度为h的满二叉树中编号为1~n的结点一一对应时,称为完全二叉树。
特点:
-
若i<n/2,则结点 i为分支结点,否则为叶子结点。
-
叶子结点只可能在层次最大的两层上出现。对于最大层次中的叶子结点,都依次排列在该层的最左边的位置上。
-
若有度为1的结点,则只可能有一个,且该结点只有左孩子没有右孩子。
-
按层序编号后,一旦出现某结点为叶子结点或只有左孩子,则编号大于i的结点均为叶子结点。
-
若n为奇数,则每个分支结点都有左孩子和右孩子;若n为偶数,则编号最大的分支结点(n/2)只有左孩子,没有右孩子,其余分支结点左右孩子都有。
- 二叉排序树
左子树上所有结点的关键字均小于根节点的关键字,右子树上的所有结点的关键字均大于根结点的关键字;左子树和右子树又各是一棵二叉排序树。
- 平衡二叉树
树上任一结点的左子树和右子树的深度之差不超过1
二叉树的性质
- 非空二叉树上的叶子结点数等于度为2的结点数加1即n0=n2+1
- 非空二叉树上第k层上至多有2^(k-1)个结点
- 高度为h的二叉树至多有2^h-1个结点
- 具有n个结点的完全二叉树的高度为[log2(n+1)]或[log2n]+1
树的存储结构
顺序存储
双亲表示法
#define MaxSize 100
typedef int ElemType;
typedef struct PTNode{ //结点结构
ElemType data; //数据域
int parent; //双亲位置
}PTNode;
typedef struct {
PTNode nodes[MaxSize]; //结点数组
int r , n ;//根的位置和结点数
}PTree;
孩子表示法
#define MaxSize 100
typedef struct CTNode{ //孩子结点
int child;
struct CTNode *next;
} *ChildPtr;
typedef struct {
ElemType data;
ChildPtr firstChild;
}CTBox;
typedef struct {
CTBox nodes[MaxSize]; //结点数组
int r , n ; //根的位置和结点数
}CTree;
孩子兄弟表示法
typedef struct CSNode{
ElemType data;
struct CSNode *firstChild , *rightChild;
}CSNode , *CSTree;
二叉树的链式存储
typedef int ElemType;
typedef struct BiTNode{
ElemType data; //数据域
struct BiTNode *lchild, *rchild; //左右孩子指针
}BiTNode ,*BiTree;
二叉树的遍历
一、先序遍历(根左右)递归
void PreOrder(BiTree T){
if (T!=NULL){
visit(T); //对根结点进行一系列操作
PreOrder(T->lchild); //遍历左子树
PreOrder(T->rchild); //遍历右子树
}
}
二、先序遍历(根左右)非递归
void PreOrder(BiTree T){
InitStack(S);//初始化一个栈
BiTree p =T ; //p为遍历指针
while(p||IsEmpty(S)){
if (p){
visit(p); //访问根结点
Push(S,p); //将结点压入栈中
p=p->lchild;
}
else{
Pop(S,p);
p=p->rchild;
}
}
}
三、中序遍历(左根右)递归
void InOrder(BiTree T){
InOrder(T->lchild);
visit(T);
InOrder(T->rchild);
}
四、中序遍历(左根右)非递归
void Inorder(BiTree T){
InitStack(S); //初始化栈
BiTree p=T ; //遍历指针
while(p||IsEmpty(S)){ //结点非空或栈非空
if (p){
Push(S,p); //将结点进栈
p=p->lchild; //向左遍历
}
else{
visit(S); //此时访问的结点为左孩子为空的结点
Pop(S,p); //出栈
p=p->rchild; //向右遍历
}
}
}
五、后序遍历(左右根)递归
void PostOrder(BiTree T){
PostOrder(T->lchild);
PostOrder(T->rchild);
visit(T);
}
六、后序遍历(左右根)非递归
void PostOrder(BiTree T){
InitStack(S); //初始化栈
BiTree p=T; //遍历指针
BiTree r=NULL;
while(p||IsEmpty(S)){
if (p){
Push(p); //进栈
p=p->lchild;
}
else{
GetTop(S,p);
if (p->rchild&&p->rchild!=r){
p=p->rchild;
}
else{
Pop(S,p);
visit(p);
r=p;
p=NULL;
}
}
}
}
七、层序遍历
void LevelOrder(BiTree T){
InitQueue(Q);//初始化队列
BiTree p=T;
EnQueue(Q,p);//入队
while(!IsEmpty(Q)){ //队列是否为空判断
visit(p); //访问该结点
DeQueue(Q,p); //出队
if (p->lchild!=NULL) //左孩子不为空,入队
EnQueue(Q,p->lchild);
if(p->rchild!=NULL) //右孩子不为空,右孩子入队
EnQueue(Q,p->rchild);
}
}
线索二叉树
- 定义:规定,若无左子树,另lchild指向其前驱结点;若无右子树,令rchild指向其后继结点。
存储结构:
typedef int ElemType ;
typedef struct ThreadNode{
ElemType data;
struct ThreadNode *lchild ,*rchild;
int ltag , rtag; //判断是左右孩子还是前驱和后继的判断依据
}ThreadNode ,*ThreadTree ;
先序线索化
ThreadNode *pre=NULL;
void Visit(ThreadNode *p){
if (p->lchild==NULL){ //判断该结点的左孩子是否为空,为空则指向其的前驱结点 ,ltag变为1
p->lchild=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->rchild==NULL){ //判断pre结点是否为空,且此时pre所指向的结点的右孩子是否为空,若为空则指向其后继结点,rtag变为1
pre->rchild=p;
pre->rtag=1;
}
pre=p; //将pre指向进行遍历的结点,进行后续操作
}
void PreThread(ThreadTree T){
if (T!=NULL){
Visit(T);
if (T->ltag==0) //需要进行判断,其是否真的是结点的左孩子,避免陷入循环
InThread(T->lchild);
InThread(T->rchild);
}
}
void CreatePreThread(ThreadTree T){
pre=NULL;
if (T!=NULL){
PreThread(T);
if (pre->rchild==NULL)
pre->rtag=1;
}
}
中序线索化
ThreadNode *pre=NULL;
void Visit(ThreadNode *p){
if (p->lchild==NULL){ //判断该结点的左孩子是否为空,为空则指向其的前驱结点 ,ltag变为1
p->lchild=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->rchild==NULL){ //判断pre结点是否为空,且此时pre所指向的结点的右孩子是否为空,若为空则指向其后继结点,rtag变为1
pre->rchild=p;
pre->rtag=1;
}
pre=p; //将pre指向进行遍历的结点,进行后续操作
}
void InThread(ThreadTree T){
if (T!=NULL){
InThread(T->lchild);
Visit(T);
InThread(T->rchild);
}
}
//中序线索化
void CreateInThread(ThreadTree T){
pre=NULL;
if (T!=NULL){
InThread(T); //
中序线索化
if (pre->rchild==NULL) //处理最后一个结点
pre->rtag=1;
}
}
后序线索化
ThreadNode *pre=NULL;
void Visit(ThreadNode *p){
if (p->lchild==NULL){ //判断该结点的左孩子是否为空,为空则指向其的前驱结点 ,ltag变为1
p->lchild=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->rchild==NULL){ //判断pre结点是否为空,且此时pre所指向的结点的右孩子是否为空,若为空则指向其后继结点,rtag变为1
pre->rchild=p;
pre->rtag=1;
}
pre=p; //将pre指向进行遍历的结点,进行后续操作
}
void PostThread(ThreadTree T){
if (T!=NULL){
PostThread(T->lchild);
PostThread(T->rchild);
Visit(T);
}
}
void CreatePostThread(ThreadTree T){
pre=NULL;
if (T!=NULL){
PostThread(T);
if (pre->rchild==NULL){
pre->rtag=1;
}
}
}
线索化二叉树的遍历
中序线索化的遍历
- 求中序线索二叉树中中序序列下的第一个结点
//中序线索二叉树中中序序列中的第一个结点
ThreadNode *FirstNode(ThreadTree T){
ThreadTree p=T;
while(T->ltag==0) //最左下结点(不一定是叶结点)
p=p->lchild;
return p;
}
- 求中序线索二叉树中结点p在中序序列下的后继
ThreadNode *NextNode(ThreadTree T){
ThreadTree p=T;
if (T->rtag==0)
return FirstNode(p->rchild);
else
return p->rchild;
}
- 不含头结点的中序线索化二叉树的遍历(中序遍历)
void Inorder(ThreadTree T){
for (ThreadNode *p= FirstNode(T);p!=NULL;p= NextNode(p)) {
visit(p);
}
}
先序线索化遍历
void PreOrder(ThreadTree T){
ThreadNode *p=T;
if (T!=NULL){
while(p){
while(p->ltag==0){ //一直向左访问
visit(p);
p=p->lchild;
}
visit(p);
p=p->rchild; //当左孩子不存在时,通过线索访问其后继
}
}
}
哈夫曼树
- 定义:带有权值的最优二叉树。
/*哈夫曼树的类型定义*/
# define N 30 //叶⼦结点的数量最⼤值
# define M 2 * N - 1 //所有结点的数量最⼤值
# define MAX 999999
typedef struct
{
int weight; //结点的权值
int parent; //双亲的下标
int LChild; //左孩⼦结点的下标
int RChild; //右孩⼦结点的下标
}HTNode, HuffmanTree[M + 1]; //HuffmanTree是⼀个结构数组类型,0号单元不⽤
/*在ht[1]⾄ht[n]的范围内选择两个parent为0且weight最⼩的结点,其序号分别赋给s1,s2*/
void Select(HuffmanTree ht, int n, int &s1, int &s2)
{
int i, min1 = MAX, min2 = MAX;
s1 = 0;
s2 = 0;
for (i = 1; i <= n; i++)
{
if (ht[i].parent == 0)
{
if (ht[i].weight < min1)
{
min2 = min1;
s2 = s1;
min1 = ht[i].weight;
s1 = i;
}
else if (ht[i].weight < min2)
{
min2 = ht[i].weight;
s2 = i;
}
}
}
}
/*创建哈夫曼树算法*/
void CrtHuffmanTree(HuffmanTree ht, int w[], int n) {
//构造哈夫曼树ht[M+1],w[]存放n个权值
int i;
for (i = 1; i <= n; i++) //1⾄n号单元存放叶⼦结点,初始化
{
ht[i].weight = w[i - 1];
ht[i].parent = 0;
ht[i].LChild = 0;
ht[i].RChild = 0;
}
int m = 2 * n - 1; //所有结点总数
for (i = n + 1; i <= m; i++) //n+1⾄m号单元存放⾮叶结点,初始化
{
ht[i].weight = 0;
ht[i].parent = 0;
ht[i].LChild = 0;
ht[i].RChild = 0;
}
/*初始化完毕,开始创建⾮叶结点*/
int s1, s2;
for (i = n + 1; i <= m; i++) //创建⾮叶结点,建哈夫曼树
{
Select(ht, i - 1, s1, s2);//在ht[1]⾄ht[i-1]的范围内选择两个parent为0且weight最⼩的结点,其序号分别赋给s1,s2
ht[i].weight = ht[s1].weight + ht[s2].weight;
ht[s1].parent = i;
ht[s2].parent = i;
ht[i].LChild = s1;
ht[i].RChild = s2;
}
}