0%

C和数据结构——二叉树实现和树的相关概念

概念知识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//感觉从这一节开始,我们学习的离散数学终于发挥真正的作用了。
//不过感觉这一节的定义好多,很麻烦。。。。
<!--more-->
//----------------------------------------------------------------
/*
树的定义:
1,domain:具有相同特性的数据元素的集合
2,relation: 1)若domian为NULL,称之为空树
2)D存在唯一的称为root的元素。这个结点对于关系R没有前驱结点
3)n>1,则其余所有的结点可以分为m个互不相交的有限集,其中每一个子集本身
又是一颗符合定义的树,称为根root的子树。这样的结点有且仅有一个前驱结点
,但是可以有很多个后继结点,就像一颗倒着的树一样。
//其实从第二条可以发现这里又是递归定义,而且效果很类似于哈斯图

表示法:
树图法
文氏图
凹入表示法:也不解释了,很简单
广义表表示法:每一层代表一级,每一级可以有多个结点(子表),依次按照哈斯图/树的关系进行展开,例如:A(B,C(E(H),F),D(G));


树的结点:包括一个数据元素,以及若干指向子树的分支
结点的度:就是每一重子树包涵的分支的个数。
树的度:树中最大的节点的度
分支节点:就是度不为零的结点。包括单双/分支结点
叶子节点:度为零的结点,也叫做终端节点
孩子节点:就是一个树(包括子树)的直接分支所在的树的集合
双亲结点:就是上一级结点称为下一级结点的双亲结点
子孙结点:一个树(子树)除了自身结点之外的所有的结点
祖先结点:相当于把子孙节点的定义反过来,就是从一个结点到达子节点的路径上的所有的结点都叫该目标结点的祖先结点
兄弟结点:同一级结点
结点的层次:根节点在第一层,依次递加
树的高度:就是树的层次最大值,也就是广义表里的深度
森林:存在不相关的两个树根或零个
有序树和无序树:就是有序积和无序积,一般只研究有序树
*/

//二叉树的递归定义:
/*
1,是二叉树或者空树
2,或者是一颗由一个根节点和两个互不相交的分别称作根节点的左子树和右子树组成的非空树
3,左子树和右子树同样是一颗二叉树
所以显然,二叉树的基本形式由5种
a空树,b三个结点的树,c两个结点的树(左右各一种),d(自身称为二叉树)

二叉树中叶子结点的个数等于度为二的结点的个数加一//这个很重要,一般可以根据这一点来求结点的个数相关的计算
所以若完全二叉树中叶子结点的个数为n,那么总结点数为2n或者2n-1;

如果完全二叉树的节点数为奇数,那么没有度为一的结点,否则有一个度为一的结点

完全二叉树
满二叉树

完全二叉树的高度=log2(n+1),n为结点的个数,可以采用极限和夹逼定理来证明(取整)

对于一个节点数为n的完全二叉树,进行顺序编号,如果
1) i=1,i为树的根节点
2)i>1,它的双亲为[i/2]
3) 2*i<=n时,那么i的左孩子为2*i,否则没有左孩子
3)若2*i+1<= n,则i的右孩子为2*i+1,否则没有孩子
*/

//----------------------------------------------------------------
//例题
/*
第一题:
一颗二叉树的总结点个数为200,其中单分支结点个数为19,求其叶子结点的个数
设单分支结点的个数为p,双分支结点的个数为q,叶子结点的个数为r,
根据性质知道:r = q +1;
n = p+ q+R;
n = 200;
q = 19;
所以 r = 91;
也就是叶子结点个数为91个;
*/

/*
第二题:
一颗完全二叉树中总结点个数为200,求叶子结点的个数
节点数为偶数,所以有一个节点数为1的结点,叶子节点和度为二的结点为2p-1,所以p= 100,
也就是说叶子结点的个数为100
*/

树的种类


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
*----------------------------------------------------------------
其实上一节(父兄二叉树)已经给了一种方法,就是对于任意一个结点
设置第一个子树根为父亲结点,它的下一级的第一个结点是兄弟节点的第一个
让它的兄弟们依次链接,同时去除父亲与除第一结点之外的联系
如此,展开之后,就构成了二叉树结构
*/

//----------------------------------------------------------------
//森林转化成树
/*
其实也很简单,一种方法就是先把所有的树转化成二叉树
然后将除了第一颗树之外的树,依次作为其前一个树的右孩子链接起来就行
*/

//还原二叉树
/*
1,如果二叉树是由一个树转换而来,就将树的最外层右子树直接链接到最上一层父亲结点的上一层的父亲结点
去除原先的连接
然后,所有左子树的位置关系以及边保持不变
伸展,得到还原树

2,如果二叉树是由森林转化而来,就断开树根的所有右子树和其父亲结点的连线
此时就已经可以得到若干个树了,然后按照树的还原方法
将树的最外层右子树直接链接到最上一层父亲结点的上一层的父亲结点


//遍历树
/*
1,先根遍历
只要树根不为空,就依次完全遍历它的每一个子树,这个过程中,对于每个子树的子树也采取同样的操作

2,后根遍历
和二叉树的后根遍历一样,对于每一个子树,都是先输出左子树,右子树,然后再输出
上一级父亲节点,依次进行,直至输出树根。
*/

//----------------------------------------------------------------
//----------------------------------------------------------------
//哈夫曼树
/*1. 为每个字符建立一个叶子节点,并加上其相应的发生频率
2. 当有一个以上的节点存在时,进行下列循环
2.1. 把这些节点作为带权值的二叉树的根节点,左右子树为空
2.2. 选择两棵根结点权值最小的树作为左右子树构造一棵新的二叉树,且至新的二叉树的根结点的权值为其左右子树上根结点的权值之和
2.3. 把权值最小的两个根节点移除
2.4. 将新的节点加入队列中
3. 最后剩下的节点即为根节点,此时二叉树已经完成
*/

//哈夫曼编码
/*
哈夫曼编码可用于构造最短代码长度方案。在数据通信中,经常需要将传送的的文字转换为二进制字符0、1,组成的二进制字符串,该过程称为编码。

哈夫曼编码就是使用哈夫曼二叉树,左节点为0,右节点为1,最后得到该位置的01路径。
哈夫曼编码的实质就是使用次数越多的字符采用的编码越短。
*/


二叉树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef char TElemType;
typedef struct BiTNode
//--------------------------------------------------------------------------------------------------------------
//二叉树的顺序存储结构
/*
完全二叉树:
存储的基础:
1,对于一个从上到下,从左到右的编号的完全二叉树
假设他的结点为2m,或者2m+1,则它的双亲结点为m.
2,同理,如果结点为2m。那么根据计算可以知道
他的子结点是{
1),左节点,4m,
2),右结点,4m+1
}
这样很简单,无需附加信息


//--------------------------------------------------------------------------------------------------------------------------------
普通的二叉树
这时候比较麻烦,无法单独仅仅存储它本身
这时候可以补全它的结点,使得它为完全二叉树,这样的结点为xu'jie'dian
虚结点的值为空。
*/
#define MAX_TREE_SIZE 100 //二叉树的最大结点数
typedef int SqBiTree[MAX_TREE_SIZE]; //这里把树的结点数据类型简单的存储为int类型 int == TreeElementType
SqBiTree bt; //
//从小标为0的结点开始存储树的根节点,将值为为#的结点记作空结点,但是这样仍然很难记录结点之间的关系
//如此这样存储,对于比较空的二叉树,空间利用率太小了

//----------------------------------------------------------------
//二叉和三叉都行
//二叉链表表示法的实现,显然二叉的空间利用率更好
/*
对于n个结点,有2n个指针域,那么就有n-1,个分支,就有n-1个非空指针域,就有n+1个空的指针域
*/
//C语言实现
typedef struct BiTNode
{
TElemType data;
BiTNode *lchild, *rchild; //指向左右的孩子,当某结点不存在左右孩子的时候,其为空NULL
} BiTNode, *BiTNode;
//----------------------------------------------------------------
//二叉树的遍历,每个结点被访问一次
/*
可以记根节点为D,左子树为L,右子树为R
根据自左到右的遍历规则,那么有
DLR//先序遍历
LDR//中序遍历
LRD//后序遍历
*/

//先序遍历
void PreOrder(BiTNode bt)
{
if (bt) //只要树(子树)根节点不是空
{
printf("%c", bt->data); //输出根节点的值
PreOrder(bt->lchild); //左子树的值
PreOrder(bt->rchild); //右子树的值
}
} //递归定义,这里要注意理解输出的过程中的不断的递归

//中序遍历
void InOrder(BiTNode bt)
{
if (bt)
{
InOrder(bt->lchild); //先输出左子树
printf("%c", bt->data); //当左子树全部输出之后,再输出根节点的值
InOrder(bt->rchild); //输出右子树
}
} //这里的递归输出相对复杂,记得多理解一下

//后序遍历
void PostOrder(BiTNode bt)
{
if (bt)
{
PostOrder(bt->lchild); //输出左子树
PostOrder(bt->rchild); //右子树
printf("%c", bt->data); //跟结点
//其实这里的每一种遍历输出都是挺麻烦的,注意实践检验和验证
}
}

//----------------------------------------------------------------
//二叉树的递归算法设计
/*
例题一:
假设二叉树中所有的结点的值为整数,采用二叉链表存储结构,求该二叉树b中所有结点值之和
*/
int SUM(BiTNode *bt)
{
int sun;
if (bt)
{
sun = SUM(bt->rchild) + SUM(bt->lchild) + SUM(bt); //不为空,就连续递归,返回下一层的值
}
else
sun = 0; //如果结点为空,就返回0;
return sun;
}

//销毁二叉树
/*
分析:显然,这里不能先销毁根节点,所以只能是后序遍历的顺序了
*/
void DestroyBTree(BiTNode b)
{ //不要傻乎乎的以为这里只是销毁了两层结点,这是一个递归算法,只要是根结点的子树,都会被释放
if (b) //只要根结点不为空
{
DestroyBTree(b->lchild); //先销毁左子树
DestroyBTree(b->rchild); //然后是右子树
free(b); //最后销毁根结点
}
else
return;
}

//-----------------------------------------------------------------------------
//实例的解决,创建二叉树
/*
如果节点为空,就构造虚结点,从而满足二叉树*/
void CreateBiTree(BiTNode &T)
{
//线序排序,还是一个递归定义
TElemType ch;
printf("请输入结点的值");
scanf("%c", &ch);
if (ch == '#') //建立一个
T = NULL;
else
{
T = (BiTNode)malloc(sizeof(BiTNode));
if (!T)
exit(-1);
T->data = ch;
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
}
}
//调用函数
int main()
{
BiTNode bt;
printf("\n创建二叉树,请输入结点的值:\n");
CreateBiTree(bt);
printf("\n先序遍历序列:\n");
PreOrder(bt);
printf("\n后序遍历:\n");
PostOrder(bt);
printf("\n");
}

//求二叉树的高度
int bt_Height(BiTNode *bt)
{
if (bt = NULL)
{
return 0;
}
else
{
if (bt->lchild == NULL && bt->rchild == NULL)
return 1;

else
return 1 + max(bt_Height(bt->lchild), bt_Height(bt->rchild));
//或者利用return (hl>hr)?:hl+1:hr+1//也就是三目运算符
}
}

//求二叉树的叶子结点的个数
int LeafCount(BiTNode *T)
{
int lnum, rnum;
if (T == NULL)
return 0;
else if (T->lchild == NULL && T->rchild == NULL)
return 1;
else
{
lnum = LeafCount(T->lchild);
rnum = LeafCount(T->rchild);
return (lnum + rnum);
}
}
//交换二叉树的左右子树
/*
由于每个结点都是以链表的形式存储,所以实质上还是对指针的操作*/
int Exchange(BiTNode *T)
{
if (T != NULL) //出口
{
if (T->lchild != NULL || T->rchild != NULL)
{
BiTNode *s = T->lchild; //先设置一个指向左子树的指针
T->lchild = T->rchild; //交换左右子树的地址
T->rchild = s; //交换左右子树的地址
Exchange(T->lchild); //递归
Exchange(T->rchild); //递归
}
}
}
//计算单分支结点的个数
int DegreeOne(BiTNode *bt)
{
int lnum, rnum, n;
if (bt == NULL) //判断根节点是否为空
return 0;
else
{ //先判断树根是否为单分支结点
if ((b->lchild == NULL && b->rchild != NULL) || (b->lchild != NULL && b->rchild != NULL))
n = 1;
else
n = 0;
}
lnum = DegreeOne(bt->lchild); //递归
rnum = DegreeOne(bt->rchild); //递归
return (lnum + rnum + n);
}

//若每个结点存储的是一个单个的字符,求二叉树中最小值的结点
void FindMinNode(BiTNode *bt, char &min) //传地址就不用直接返回参数了
{
if (bt == NULL)
return NULL;
else
{
if (bt->data < min) //循环体
min = bt->data; //循环体
FindMinNode(bt->lchild, min); //递归调用
FindMaxNode(bt->rchild, min); //递归调用
}
}