王道数据结构——树与二叉树

本文主要讲述了数据结构中树的基本概念,二叉树,树与森林以及树与二叉树的应用。

知识框架如下图所示:

树的基本概念

树是N(N>=0)个结点的有限集合,N=0时,称为空树。

而任何一棵非空树应该满足,有且仅有一个根结点,当N>1时,其余结点又可以分为几个互不相交的有限集合,其本身又构成一个树(体现递归的定义),称为根结点的子树。需要特别说明的一个重要结论是,任何一个有N(N>=1)个结点的树都有N-1条边。

二叉树的基本概念

二叉树是一种特殊的树形结构,其特点是不存在度大于2的结点,且每个结点的子树(若有)均存在左右之分,其次序不能颠倒。

严格一点来说,二叉树是n(n>=0)个结点的有限集合。

1,n=0时,表示空二叉树。

2,n>0时,由根结点和互不相交的被称为根的左子树和右子树组成,而左子树和右子树也分别是一颗二叉树。

几种特殊的二叉树:满二叉树,完全二叉树,排序二叉树,平衡二叉树。

 二叉树的存储结构

顺序存储结构,将一棵二叉树用0填充成一棵满二叉树,对于每个结点i,双亲结点为floor(i/2),左孩子为2*i,右孩子为2*i+1。

 1 //二叉树的顺序存储结构
 2 #define MAX_TREE_SIZE 100
 3 typedef int SqBiTree[MAX_TREE_SIZE];
 4 SqBiTree bt;
 5 
 6 //二叉树的链式存储结构
 7 typedef struct BiTNode{
 8     int data;
 9     struct BiTNode *lchild, *rchild; 
10 }BiTNode, *BiTree;

 

 1 #include<cstdio>
 2 
 3 //二叉树的顺序存储结构
 4 #define MAX_TREE_SIZE 100
 5 typedef int SqBiTree[MAX_TREE_SIZE];
 6 SqBiTree bt;
 7 
 8 //在顺序结构中寻找i和j的最近共同祖先 
 9 void Comm_Ancester(SqBiTree bt, int i, int j){
10     int a = i, b = j;
11     if(bt[i] == 0){
12         printf("编号为%d的结点不存在!\n", i); 
13         return;
14     }
15     if(bt[j] == 0){
16         printf("编号为%d的结点不存在!\n", j);
17         return;
18     }
19     
20     while(i != j){//谁大谁往上跳一层再比,直到相等 
21         if(i > j)
22             i /= 2;
23         else
24             j /= 2;
25     }
26     printf("编号为%d的结点和编号为%d的结点的最近公共祖先的结点编号为%d,其值为%d\n", a, b, i, bt[i]);
27 }
28 
29 int main()
30 {
31     int bt[] = {-1, 1, 2,3, 4,5,6,7, 8,0,10,0,0,0,0,0, 0,0,0,0};//层次序列 
32     /*
33                  1
34              /       \
35           2            3
36         /   \        /   \ 
37        4     5      6     7
38      /  \  /   \   / \   / \
39     8   0 10   0  0  0  0  0
40    / \   / \
41   0  0  0  0
42     */ 
43     //在上述顺序存储结构中寻找编号为i和j结点的最近共同祖先结点的编号和值 
44     Comm_Ancester(bt, 2, 3);
45     Comm_Ancester(bt, 4, 5);
46     Comm_Ancester(bt, 4, 6);
47     Comm_Ancester(bt, 8, 10);
48     /*
49     编号为2的结点和编号为3的结点的最近公共祖先的结点编号为1,其值为1
50     编号为4的结点和编号为5的结点的最近公共祖先的结点编号为2,其值为2
51     编号为4的结点和编号为6的结点的最近公共祖先的结点编号为1,其值为1
52     编号为8的结点和编号为10的结点的最近公共祖先的结点编号为2,其值为2
53     */ 
54     
55     return 0;    
56 } 

链式存储结构,类似定义双向链表,不同的是指向左右孩子结点

  1 #include<cstdio>
  2 #include<cstdlib>
  3 #include<cstring>
  4 #include<algorithm>
  5 using namespace std;
  6 
  7 //二叉树的链式存储结构
  8 typedef struct BiTNode{
  9     char data;
 10     struct BiTNode *lchild, *rchild; 
 11 }BiTNode, *BiTree;
 12 
 13 //二叉树的顺序存储结构
 14 #define MaxSize 500
 15 typedef struct{
 16     BiTree data[MaxSize];
 17     int front,rear;
 18 } SqQueue;
 19 void InitQueue(SqQueue &Q)//初始化
 20 {
 21     Q.front = Q.rear = 0;
 22 }
 23 bool QueueEmpty(SqQueue Q)//队列是否为空
 24 {
 25     if(Q.front == Q.rear)
 26         return true;//空为真,非空为假 
 27     return false; 
 28 }
 29 bool EnQueue(SqQueue &Q, BiTree x)//若队列未满,入队
 30 {
 31     //if(Q.rear == MaxSize) return false;//判断条件错误,可能假溢出 
 32     Q.data[Q.rear++] = x;//假定不会假溢出 
 33     return true; 
 34 } 
 35 bool DeQueue(SqQueue &Q, BiTree &x)//若队列非空,出队
 36 {
 37     if(Q.front == Q.rear)
 38         return false;
 39     x = Q.data[Q.front++];
 40     return true;
 41 }
 42 bool GetHead(SqQueue Q, BiTree &x)//读取队头元素,若队列非空,将队头元素赋值给x
 43 {
 44     if(Q.front == Q.rear)
 45         return false;
 46     x = Q.data[Q.front];
 47     return true;
 48 }
 49 void ClearQueue(SqQueue &Q)//清空队列,并回收内存
 50 {
 51     Q.front = Q.rear = 0;
 52 }
 53 
 54 //初始化一棵二叉树 
 55 void InitBiTree(BiTree &bt){
 56     bt = (BiTree)malloc(sizeof(BiTNode));
 57 } 
 58 //按照先根序列创建一棵二叉树 
 59 const char *str = "123004507006000";
 60 void CreateBiTree(BiTree &bt){
 61     //printf("%s\n", str);
 62     int ch = str[0];
 63     str++; 
 64     if(ch == '0')
 65         bt = NULL;
 66     else{
 67         bt = (BiTNode*)malloc(sizeof(BiTNode));
 68         bt->data = ch;
 69         CreateBiTree(bt->lchild);
 70         CreateBiTree(bt->rchild);
 71     }
 72 }
 73 //清空一棵二叉树 
 74 void ClearBiTree(BiTree &bt){
 75     if(bt != NULL){
 76         ClearBiTree(bt->lchild);
 77         ClearBiTree(bt->lchild);
 78         bt = NULL;
 79     }
 80 }
 81 
 82 //判断该二叉树是否为空 
 83 bool BiTreeEmpty(BiTree bt){
 84     if(bt == NULL)
 85         return true;
 86     return false; 
 87 }
 88 //返回该二叉树的深度 
 89 int BiTreeDepth(BiTree bt){
 90     if(bt != NULL){
 91         int l = 1 + BiTreeDepth(bt->lchild);
 92         int r = 1 + BiTreeDepth(bt->rchild);
 93         return max(l,r);
 94     }
 95 } 
 96 //先序遍历 
 97 void PreOrderTraverse(BiTree bt){
 98     if(bt != NULL){
 99         printf("%c", bt->data);
100         PreOrderTraverse(bt->lchild);
101         PreOrderTraverse(bt->rchild);
102     }
103 }
104 //中序遍历 
105 void InOrderTraverse(BiTree bt){
106     if(bt != NULL){
107         InOrderTraverse(bt->lchild);
108         printf("%c", bt->data);
109         InOrderTraverse(bt->rchild);
110     }
111 }
112 //后序遍历 
113 void PostOrderTraverse(BiTree bt){
114     if(bt != NULL){
115         PostOrderTraverse(bt->lchild);
116         PostOrderTraverse(bt->rchild);
117         printf("%c", bt->data);
118     }
119 }
120 //层次遍历 
121 void levelOrderTraverse(BiTree bt){
122     SqQueue Q;
123     InitQueue(Q);
124     BiTree p;
125     EnQueue(Q, bt);
126     while(!QueueEmpty(Q)){
127         DeQueue(Q, p);
128         printf("%c", p->data);
129         if(p->lchild != NULL)
130             EnQueue(Q, p->lchild);
131         if(p->rchild != NULL)
132             EnQueue(Q, p->rchild);
133     }
134 }
135 
136 int main()
137 {
138     /*
139                1
140              /   \    
141            2      0      
142          /    \         
143        3       4      
144       / \   /   \          
145      0  0  5     6         
146           / \   / \
147             0  7  0  0
148               / \
149              0      0
150    */ 
151     //对于形如上图的二叉树 
152     BiTree bt;
153     InitBiTree(bt);
154     //char str[] = "123004507006000";//先序次序,声明在前面的全局变量 
155     CreateBiTree(bt); 
156     PreOrderTraverse(bt);puts("");
157     InOrderTraverse(bt);puts("");
158     PostOrderTraverse(bt);puts("");
159     levelOrderTraverse(bt);puts("");
160     
161     printf("该数的深度是%d\n",BiTreeDepth(bt));//计算深度并输出 
162     ClearBiTree(bt);
163     if(BiTreeEmpty(bt))
164         printf("清空成功!\n"); 
165     /*
166     1234576
167     3257461
168     3756421
169     1234567
170     该数的深度是5
171     清空成功!
172     */ 
173     return 0;    
174 } 

 线索二叉树

  传统的链式存储仅能体现一种父子关系,不能直接得到结点在遍历中的前驱和后继。而二叉树的链式存储结构中存在大量的空指针,若能利用这些空链域存放指向其直接前驱或后继结点的指针,则可以加快查找前驱结点和后继结点的速度。

  1 #include<cstdio>
  2 #include<cstdlib>
  3 
  4 //线索二叉树的链式存储结构
  5 /*
  6     左前右后 
  7     ltag :0 lchild域指向结点的左孩子
  8          :1 lchild域指向结点的前驱
  9     rtag :0 rchild域指向结点的右孩子
 10          :1 rchild域指向结点的后继
 11 */ 
 12 typedef struct TreadNode{
 13     char data;
 14     struct TreadNode *lchild, *rchild;
 15     int ltag, rtag; 
 16 }ThreadNode, *ThreadTree;
 17  
 18 void InitThreadTree(ThreadTree &bt){
 19     bt = (ThreadTree)malloc(sizeof(ThreadNode));
 20     bt->lchild = bt->rchild = NULL;
 21     bt->ltag = bt->rtag = 0;
 22 } 
 23 //按照先根序列创建一棵二叉树 
 24 const char *str = "12040035000";
 25 void CreateThreadTree(ThreadTree &bt){
 26     //printf("%s\n", str);
 27     int ch = str[0];
 28     str++; 
 29     if(ch == '0')
 30         bt = NULL;
 31     else{
 32         bt = (ThreadNode*)malloc(sizeof(ThreadNode));
 33         bt->data = ch;
 34         bt->lchild = bt->rchild = NULL;
 35         bt->ltag = bt->rtag = 0;
 36         CreateThreadTree(bt->lchild);
 37         CreateThreadTree(bt->rchild);
 38     }
 39 }
 40 
 41 /*线索二叉树的构造,即二叉树的线索化,实质上就是遍历一遍二叉树,
 42 发现该结点的左右指针域是否为空,若为空,分别将其改造成指向前驱和后继结点即可,
 43 不要忘记改变标志位的状态*/ 
 44 void InThread(ThreadTree &p, ThreadTree &pre){
 45     if(p != NULL){
 46         InThread(p->lchild, pre);//线索化左子树
 47          
 48         if(p->lchild == NULL){
 49             p->lchild = pre;
 50             p->ltag = 1;
 51         }
 52         if(pre != NULL && pre->rchild == NULL){
 53             pre->rchild = p;
 54             pre->rtag = 1;
 55         }
 56         pre = p;//标记当前结点位刚刚访问过的结点            
 57         InThread(p->rchild, pre);//递归线索化右子树
 58     }
 59 } 
 60 //通过中序遍历建立中序线索二叉树的主过程如下 
 61 void CreateInThread(ThreadTree &T){
 62     ThreadTree pre = NULL;
 63     //借助一个pre指针指向中序遍历时上一个刚刚访问过的结点,表示各节点的前后关系 
 64     if(T != NULL){
 65         InThread(T, pre);
 66         pre->rchild = NULL;//处理遍历的最后一个结点 
 67         pre->rtag = 1;
 68     }
 69 }
 70 //二叉树的中序遍历算法 
 71 void InOrderTraverse(ThreadTree bt){
 72     if(bt != NULL){
 73         InOrderTraverse(bt->lchild);
 74         printf("%c", bt->data);
 75         InOrderTraverse(bt->rchild);
 76     }
 77 }
 78 //求中序线索二叉树中中序序列下的第一个结点 
 79 ThreadNode* Firstnode(ThreadNode *p){
 80     while(p->ltag == 0)//记得创建结点时,tag初始化为0,不然找不到中序第一个结点 
 81         p = p->lchild;//最左下结点(不一定是叶子结点) 
 82     
 83     return p;
 84 }
 85 //求中序线索二叉树中结点p在中序序列下的后继结点 
 86 ThreadNode* Nextnode(ThreadNode *p){
 87     if(p->rtag == 0)    return Firstnode(p->rchild);//rtag == 0,右孩子存在,返回其右子树的第一个结点 
 88     else return p->rchild;//rtag == 1,直接返回其右孩子指针指向的后继结点 
 89 } 
 90 //不含头结点的中序线索二叉树的中序遍历算法
 91 void Inorder(ThreadNode *T){
 92     for(ThreadNode *p = Firstnode(T); p != NULL; p = Nextnode(p))
 93         printf("%c", p->data);//记得创建结点时,tag初始化为0,不然找不到中序第一个结点  
 94 } 
 95 int main()
 96 {
 97     /*
 98                 1
 99              /    \    
100            2       3      
101           / \     / \    
102          0  4    5  0
103            / \  / \
104           0  0 0  0
105    */ 
106     //对于形如上图的二叉树线索化 
107     ThreadTree bt;
108     InitThreadTree(bt);
109     //char str[] = "12040035000";//先序次序,声明在前面的全局变量 
110     CreateThreadTree(bt);
111     puts("二叉树中序遍历:");
112     InOrderTraverse(bt);puts(""); 
113     
114     CreateInThread(bt);
115     puts("线索二叉树中序遍历:");
116     Inorder(bt);puts("");
117     /*
118     二叉树中序遍历:
119     24153
120     线索二叉树中序遍历:
121     24153
122     */ 
123     return 0;    
124 } 

树的存储结构

双亲表示法

 1 #include<cstdio>
 2 #include<cstring> 
 3 //树的双亲表示法 
 4 #define MAX_TREE_SIZE 100
 5 typedef struct{
 6     char data;
 7     int parent;
 8 }PTNode;
 9 typedef struct{
10     PTNode nodes[MAX_TREE_SIZE];
11     int n = 0;
12 }PTree;
13  
14 int main()
15 {
16     /*
17     对于如下的二叉树
18                  0
19             1    2    3
20          4   5        6
21                     7 8 9 
22     */
23     PTree pt;
24     int e[20][2]={0,-1, 1,0,2,0,3,0, 4,1,5,1, 6,3,7,6,8,6,9,6};
25     for(int i = 0; i < 10; i++){
26         pt.nodes[i].data = e[i][0];
27         pt.nodes[i].parent = e[i][1];
28         pt.n++;
29     } 
30     return 0;
31 }

孩子表示法

 1 /*树的孩子表示法 
 2 每个结点的孩子结点都用一个单链表存储其孩子结点,则N个结点就有N个单链表 
 3 */
 4 #define MAX_TREE_SIZE 100
 5 typedef struct CTNode{//孩子结点 
 6     int child;
 7     struct CTNode *next;
 8 }*ChildPtr;
 9 typedef struct{
10     char data;
11     ChildPtr firstchild;//孩子链表头指针 
12 }CTBox;
13 typedef struct{
14     CTBox nodes[MAX_TREE_SIZE];
15     int n, r;//结点数和根的位置 
16 }CTree; 

左孩子右兄弟表示法

1 /*树的左孩子右兄弟表示法 
2 类似二叉树的链式存储结构,最大的优点时实现树转化为二叉树的操作,易于查找孩子结点,缺点是不利于查找双亲结点,
3 可以使用添加parent域指向其父结点的方法解决 
4 */ 
5 typedef struct CSNode{
6     char data;
7     struct CSNode *firstchild, *nextsibling; 
8 }CSNode, *CSTree; 

 

posted @ 2019-11-26 23:31  Reqaw  阅读(863)  评论(0编辑  收藏  举报