树和二叉树的知识总结
思维导图
树和二叉树的基本概念和算法
思维导图
树和二叉树的基本概念和算法****
1树的基本概念
树的定义:树(Tree)是n(n≧0)个结点的有限集。n=0时称为空树。在任意一颗非空树中:有且仅有一个特定的称为根的结点。当
n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、T3……、Tm,其中每个集合本身又是一棵树,并且称为根的子树。
树的基本术语:
( b ) ------------------------ ( c )
1.结点: 树中的一个独立单元,包含一个数据元素及若干指向其子树的分支;如:图C中的A、B、C。
2.结点的度:结点拥有的子树数; 如:图c中A的度为3。
3.树的度:树内各节点度的最大值;如图C的度为3;树中所有结点最大度数为m的有序树称为 m叉树。
4.叶子(终端节点):度为0的结点;如:E、F等。
5.非终端结点(分支节点):度不为0的结点;除根节点外,非终端结点也称为内部结点。
6.双亲和孩子:结点的子树的根称为该节点的孩子,该节点称为孩子的双亲:如:B的双亲为A;B的孩子有E和F。
7.兄弟:同一个双亲的孩子之间互称兄弟;如E、F互为兄弟。
8.祖先:从根到该节点所经分支上的所有结点;如:F的祖先是A、B。
9.子孙:以某结点为根的子树中的任一结点;如:A的子孙有:E、B、H等。
10.层次:结点的层次从根开始定义起,树中任一结点的层次都等于双亲结点的层次加1。
11.堂兄弟:双亲在同一层的结点互为堂兄弟,如G和H互为堂兄弟。
12.树的深度:树中结点的最大层次称为树的深度或者高度。
13.有序树和无序树:如果将树中结点的各子树看成从左到右是有次序的,则为有序树;树的最左边为第一个孩子,最右边为最后一个孩子。
14.森林:是x个互不相交的树的集合。
树的性质:
性质一:树中结点数等于所有节点的度数和加一。
性质二:度为m的树中第i层最多有m的i-1次方个节点(i>=1)。
性质三:高度h的m次树最多有m的h次方再-1后除以m-1。
性质四:具有n个节点的m次树的最小高度为logm(n(m-1)+1)。
树的基本运算:
1.先根遍历:先访问根节点,后按照从左到右的顺序先根遍历根节点的每一棵子树。
2.后跟遍历:先按照从左到右的顺序遍历根节点的每一棵子树后遍历根节点。
3.层次遍历:从根节点开始从上往下从左到右的次序访问树中的每一个节点。
树的存储结构:双亲存储结构,孩子链存储结构,孩子兄弟链存储结构。
2二叉树的概念和性质
二叉树的定义:1.定义:把满足以下两个条件的树型结构叫做二叉树
1) 每个结点的度都不大于 2;(2) 每个结点的孩子结点次序不能任意颠倒。
由此定义可看出,一个二叉树中的每个结点只能含有 0、1 或 2 个孩子,而且每个孩子有左右之分。位于左边的孩子叫做左孩子,位于右边的孩子叫做右孩子。
二叉树的性质:
性质一:非空二叉树上的叶子节点数等于双分支结点数加一。
性质二:非空二叉树的第i层上最多有2的i减一次方个节点。
性质三:高度为h的二叉树最多有2deh次方再减一。
二叉树与树森林的转换(详细例子于课本201页):
1.树转换为二叉树
树中每个节点最多只有一个最左边的孩子(长子)和一个右邻的兄弟
1)在所有兄弟节点之间加一连线
2)每个节点,除了保留与其长子之间的连线外,去掉该节点与其它孩子的连线
3)层次调整。以树根点为轴心,将整棵树顺时针旋转一定的角度,使之结构层次分明。这样结点的左孩子还是之前的左孩子,右孩子是之前的兄弟结点
2.森林转化为二叉树
1)把每棵树转换为二叉树。
2)第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连接起来。
3.二叉树转换为树
是树转换为二叉树的逆过程。
1)加线。若某结点X的左孩子结点存在,则将这个左孩子的右孩子结点、右孩子的右孩子结点、右孩子的右孩子的右孩子结点…,都作为结点X的孩子。将结点X与这些右孩子结点用线连接起来。
2)去线。删除原二叉树中所有结点与其右孩子结点的连线。
3)层次调整。
4.二叉树转换为森林
假如一棵二叉树的根节点有右孩子,则这棵二叉树能够转换为森林,否则将转换为一棵树。
1)从根节点开始,若右孩子存在,则把与右孩子结点的连线删除。再查看分离后的二叉树,若其根节点的右孩子存在,则连线删除…。直到所有这些根节点与右孩子的连线都删除为止。
2)将每棵分离后的二叉树转换为树。
3二叉树的存储结构
顺序存储结构:二叉树的顺序存储结构是指用一组地址连续的存储单元依次自上而下、自左至右存储完全二叉树上的结点元素,即将完全二叉树上编号为i的结点元素存储在某个数组下标为i-1的分量中,然后通过一些方法确定结点在逻辑上的父子和兄弟关系。完全二叉树和满二叉树采用顺序存储比较合适,节省空间且数组元素下标值能确定结点在二叉树中的位置和结点之间的关系。但对于一般二叉树,则需要添加一些并不存在的空结点,所以效率并不高。
顺序存储示意图如下:
链式存储结构: 链式结构是指用一个链表来存储一棵二叉树,二叉树中的每个结点用链表的一个链结点来存储。在二叉树中,结点结构通常包括若干数据域和若干指针域。二叉链表至少包含3个域:数据域data、左指针域lchild和右指针域rchild,若下图所示:
常用的二叉链表存储结构如下图所示:
代码如下:
typedef struct BiTNode{
ElemType data; //数据域
struct BiTNode *lchild, *rchild; //左、右孩子指针
}BiTNode, *BiTree;
4二叉树的基本运算及实现(详见课本207页)
1.创建二叉树
2.销毁二叉树
3.查找节点
4.找孩子节点
5.求高度
6.输出二叉树
5二叉树的遍历
先序遍历:先访问根节点,后先序遍历左子树,在先序遍历右子树(个人理解:根>左>右)。
void PreOrder(BiTree t)
{
if(t){
cout<<t->data;
PreOrder(t->lchild);
PreOrder(t->rchild);
}
}
中序遍历:中序遍历左子树,后访问根节点,最后中序遍历右子树(个人理解:先最左节点)。
{
if(t){
InOrder(t->lchild);
cout<<t->data;
InOrder(t->rchild);
}
}
后序遍历:后续遍历左子树,再后序遍历右子树,最后访问根节点(个人理解:左>右>根)。
void PostOrder(BiTree t)
{
if(t){
PostOrder(t->lchild);
PostOrder(t->rchild);
cout<<t->data;
}
}
层次遍历:从上自下从左到右访问节点。
遍历的递归算法详见课本212页,非递归算法课本218页。
6二叉树的构造
这里的构造二叉树是根据二叉树的中序序列和后序序列来构造一棵二叉树,我们容易发现,在后序序列当中从后往前看就是树的一个个“根”节点,因此我们得到了根节点就可以将中序序列分成两个部分(这也符合二叉树的定义),这样后序序列也相应的被分为除了最后一个根节点的两个部分,即根的左子树和右子树(与中序序列对应),然后再从后序序列的左子树中找到根,右子树中找到根,递归构造即可。(代码详见230页)
7线索二叉树
线索二叉树的概念:
n个结点的二叉链表中含有n+1个空指针域。利用二叉链表中的空指针域,存放指向结点在某种遍历次序下的前趋和后继结点的指针(这种附加的指针称为"线索")。这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。
线索化二叉树:
8哈夫曼树
哈夫曼树(Huffman Tree),又称最优二叉树,是一类带权路径长度最短的树。假设有n个权值{w1,w2,...,wn},如果构造一棵有n个叶子节点的二叉树,而这n个叶子节点的权值是{w1,w2,...,wn},则所构造出的带权路径长度最小的二叉树就被称为赫夫曼树。
这里补充下树的带权路径长度的概念。树的带权路径长度指树中所有叶子节点到根节点的路径长度与该叶子节点权值的乘积之和,如果在一棵二叉树中共有n个叶子节点,用Wi表示第i个叶子节点的权值,Li表示第i个也叶子节点到根节点的路径长度,则该二叉树的带权路径长度 WPL=W1L1 + W2L2 + ... Wn*Ln。
根据节点的个数以及权值的不同,哈夫曼树的形状也各不相同,哈夫曼树具有如下特性:
1.对于同一组权值,所能得到的哈夫曼树不一定是唯一的。
2.哈夫曼树的左右子树可以互换,因为这并不影响树的带权路径长度。
3.带权值的节点都是叶子节点,不带权值的节点都是某棵子二叉树的根节点。
4.权值越大的节点越靠近赫夫曼树的根节点,权值越小的节点越远离赫夫曼树的根节点。
5.哈夫曼树中只有叶子节点和度为2的节点,没有度为1的节点。
6.一棵有n个叶子节点的赫夫曼树共有2n-1个节点。
哈夫曼树的构建步骤如下:
1、将给定的n个权值看做n棵只有根节点(无左右孩子)的二叉树,组成一个集合HT,每棵树的权值为该节点的权值。
2、从集合HT中选出2棵权值最小的二叉树,组成一棵新的二叉树,其权值为这2棵二叉树的权值之和。
3、将步骤2中选出的2棵二叉树从集合HT中删去,同时将步骤2中新得到的二叉树加入到集合HT中。
4、重复步骤2和步骤3,直到集合HT中只含一棵树,这棵树便是赫夫曼树。
假如给定如下5个权值:
则按照以上步骤,可以构造出如下面左图所示的哈夫曼树,当然也可能构造出如下面右图所示的哈夫曼树,这并不是唯一的。
Huffman编码
哈夫曼树的应用十分广泛,比如众所周知的在通信电文中的应用。在等传送电文时,我们希望电文的总长尽可能短,因此可以对每个字符设计长度不等的编码,让电文中出现较多的字符采用尽可能短的编码。为了保证在译码时不出现歧义,我们可以采取如下图所示的编码方式:
即左分支编码为字符0,右分支编码为字符1,将从根节点到叶子节点的路径上分支字符组成的字符串作为叶子节点字符的编码,这便是赫夫曼编码。我们根据上面左图可以得到各叶子节点的赫夫曼编码如下:
权值为5的节点的哈夫曼编码为:11
权值为4的节点的哈夫曼编码为:10
权值为3的节点的哈夫曼编码为:00
权值为2的节点的哈夫曼编码为:011
权值为1的节点的哈夫曼编码为:010
而对于上面右图,则可以得到各叶子节点的哈夫曼编码如下:
权值为5的节点的哈夫曼编码为:00
权值为4的节点的哈夫曼编码为:01
权值为3的节点的哈夫曼编码为:10
权值为2的节点的哈夫曼编码为:110
权值为1的节点的哈夫曼编码为:111
最小带权路径长度(左图):WPL=23+31+32+24+25=33
最小带权路径长度(右图):WPL=25+24+23+32+31=33
由此可见,不管是左子树权值大于右子树权值还是小于右子树权值的哈夫曼树的最小带权路径长度不变。
例题(实验课)
#include<iostream>
using namespace std;
typedef struct BiTNode {
struct BiTNode* lchild, * rchild;
int data;
}BiTNode,*BiTree;
BiTree InsertBST(BiTree& T, int key);
BiTree CreateBST(BiTree& T);
void InOrder(BiTree T);
bool Delete(BiTree& p);
bool DeleteBST(BiTree& T, int key);
BiTNode* SearchBST(BiTNode* &t, int key)
{
if (!t || t->data == key)
return t;
else if (key > t->data)
return (SearchBST(t->rchild, key));
else
return (SearchBST(t->lchild, key));
}
BiTree InsertBST(BiTree &T,int key)
{
if (T == NULL) {
T = new BiTNode;
T->data = key;
T->rchild = T->lchild = NULL;
}
else if (T->data == key) return NULL;
else if (T->data > key) return InsertBST(T->lchild, key);
else return InsertBST(T->rchild, key);
}
BiTree CreateBST(BiTree& T) {
T = NULL;
int m[1000], n;
int key = 0;
cout << "请输入节点的个数";
cin >> n;
for (int i = 0; i < n; i++) {
cin >> m[i];
}
for (int i = 0; i < n; ++i) {
key = m[i];
InsertBST(T, key);
}
return T;
}
void InOrder(BiTree T) {
if (T != NULL) {
InOrder(T->lchild);
cout << T->data << " ";
InOrder(T->rchild);
}
}
bool Delete(BiTree& p)
{
BiTree q, s;
if (p->rchild == NULL) {
q = p;
p = p->rchild;
delete q;
}
else if (p->lchild == NULL) {
q = p;
p = p->rchild;
delete q;
}
else {
q = p;
s = p->lchild;
while (s->rchild) {
q = s;
s = s->rchild;
}
p->data = s->data;
q->lchild = s->lchild;
delete s;
}
return true;
}
bool DeleteBST(BiTree& T, int key)
{
if (T == NULL)
return false;
else{
if (key < T->data)
DeleteBST(T->lchild, key);
else if (key > T->data)
DeleteBST(T->rchild, key);
else {
return Delete(T);
}
}
}
int main() {
BiTree T;
int key;
CreateBST(T);
InOrder(T);
cout << endl;
cout << "请输入删除的关键字:";
cin >> key;
DeleteBST(T, key);
InOrder(T);
cout << "请输入你要查找的节点:";
int x;
cin >> x;
if (SearchBST(T, key))
cout << "存在"<<endl;
else cout << "不存在" << endl;
return 0;
}
难点在于删除函数,不懂的问题看书本,与同学交流合作,共同解决