树、二叉树、查找算法总结
目录
思维导图
(学艺不精,见谅见谅)
(以下内容根据思维导图哈)
-
树
1.树的定义:树(Tree)是n(n≥0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:(1)有且仅有一个特定的称为根(Root)的结点;(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、……、 Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。
注:(1)b>0时根结点是唯一的,不可能存在多个根结点,别和现实中的大树混在一起,现实中的大树有很多根须,那是真实的树,数据结构中的树只能有一个根结点。
(2)m>0时,子树的个数没有限制,但它们一定是互不相交的。
2.相关概念:
(1)结点的度:结点拥有的子树数称为结点的度(Degree)。
(2)叶结点:度为0的结点称为叶结点(Leaf)或终端结点。
(3)树的度:树的度是树内各结点的度的最大值。
(4)结点之间关系:结点的子树的根称为该结点的孩子(Child),相应地,该结点称为孩子的双亲。同一个双亲的孩子之间互称为兄弟(Sibling)。结点的祖先是从根到该结点所经分支上的所有结点。
(5)结点的层次:结点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层。若某结点在第l层,则其子树的根就在第l+1层。其双亲在同一层的结点互为堂兄弟。
(6)有/无序树:如果将树中结点的子树看成从左至右是有次序的,不能互换的,则该树称为有序树,否则称为无序树。
3.特点:
①每个节点有零个或多个子节点;
②没有父节点的节点称为根节点;
③每一个非根节点有且只有一个父节点;
④除了根节点外,每个子节点可以分为多个不相交的子树;
4.树的存储结构:双亲表示法,孩子表示法,双亲孩子表示法,孩子兄弟表示法。
(1)双亲表示法:用指针表示出每个节点的双亲节点
typedef int DataType;
struct Node{
struct Node* Parent;//指向双亲节点指针域
DataType data;//节点中的数据
};
(2)孩子表示法: 用指针指出每个节点的孩子节点
typedef int DataType;
struct Node{
struct Node* Child1;
struct Node* Child2;
struct Node* Child3;
DataType data;
};
(3)双亲孩子表示法:用指针既表示出每个节点得双亲节点,也表示出每个节点的孩子节点。即双亲表示法+孩子表示法。
typedef int DataType;
struct Node{
struct Node* Parent;
struct Node* Child1;
struct Node* Child2;
struct Node* Child3;
DataType data;
};
(4)孩子兄弟表示法:即表示出每个节点的第一个孩子节点,也表示出每个节点的下一个兄弟节点。
typedef int DataType;
struct Node{
struct Node* Child1;//第一个孩子节点
struct Node* NextBrother;//指向其下一个兄弟节点
DataType data;//节点中的数据域
};
-
二叉树
1.定义:二叉树(Binary Tree)是n(n>=0)个结点的一个有限集合,该集合或者为空集(称为空二叉树),或者是由一个根节点加上两棵互不相交的、分别称为左子树和右子树的二叉树组成。
2.特点:每个节点最多有两棵子树,即二叉树不存在度大于2的节点。二叉树的子树有左右之分,其子树的次序不能颠倒。
3.特殊二叉树:
(1) 满二叉树:高度为h,并且含有(2^h)-1个结点的二叉树
对于编号为 i 的结点,如果有双亲,双亲为 i/2 向下取整;如果有左孩子,左孩子编号为 2i,如果有右孩子,右孩子编号为 (2i + 1)。
(2)完全二叉树:若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
如果有度为 1 的结点,那只可能有一个,且该节点只有左孩子,而无右孩子。
4.性质:
性质1 在二叉树的第 i 层上至多有 2^(i-1)个 结点(i>=1)
性质2 深度为k的二叉树至多有2^k-1个结点(k≥1)。
性质3 在任意-棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则no=n2+1。
性质4:具有n个结点的完全二叉树的深度为【log2n】+1(【x】表示不大于x的最大整数)。
性质5 :
对完全二叉树按从上到下、从左到右的顺序依次编号1,2,…,n,则有一下关系:
1) 当i>1时,结点i的双亲结点标号为向下取整(i/2),即当i为偶数时,其双亲结点的编号为i/2,他是双亲结点的左孩子;当i为奇数时,其双亲结点的编号为(i-1)/2,他是双亲结点的右孩子。
2)当2i<=n时,结点i的左孩子编号为2i,否则无左孩子
3) 当2i+1<=n时,结点i的右孩子编号为2i+1,否则无右孩子
5.存储结构
(1)顺序存储结构
实际上就是用一个一维数组按照自上而下,自左至右的将每一层的结点放入数组中
#define MAXSIZE 100
typedef TElemType SqBiTree[MAXSIZE];
SqBiTree bt;
2)链式存储方式
实际上就是一个链表,链表的结点至少得包含三个域:数据域和左右指针域(为了方便找到结点的双亲,还可以在结点的结构中增加一个指向其双亲结点的指针域)
typedef struct BiTNode{
TElemType data; //结点的数据域
struct BiTNode *lchild,*rchild; //左右孩子指针
}BiTNode,*BiTree;
6.二叉树的遍历:遵循某种次序,遍历二叉树中的所有节点,使得每个节点被访问一次,而且仅访问一次。“访问”:即对节点施行某些操作。
(1)先序遍历:先访问根节点,再访问左子树,最后访问右子树。
void PreOrederTraverse(BiTree T)
{
if(T==NULL)
return;
printf("%c",T->data);
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
(2)中序遍历:先访问左子树,再访问根节点,最后访问右子树。
void InOrederTraverse(BiTree T)
{
if(T==NULL)
return;
InOrderTraverse(T->lchild);
printf("%c",T->data);
InOrderTraverse(T->rchild);
}
(3)后序遍历:先访问左子树,再访问右子树,最后访问根节点。
void PostOrederTraverse(BiTree T)
{
if(T==NULL)
return;
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
printf("%c",T->data);
}
(4)层序遍历:同一层中按左子树再右子树的次序遍历,从根节点层到叶节点层访问。
7.二叉树建立
-
树、二叉树、森林转化
1、森林转二叉树
(1)、把每棵树转换为二叉树
(2)、第一棵二叉树不动,从第二棵二叉树开始,一次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连接起来。
2、树转二叉树
(1)、加线。在所有的兄弟结点之间加一条线。
(2)、去线。树中的每个结点,只保留它与第一个孩子结点的连线,删除其他孩子结点之间的连线。
(3)、调整。以树的根结点为轴心,将整个树调节一下(第一个孩子是结点的左孩子,兄弟转过来的孩子是结点的右孩子)
3、二叉树转树
(1)、加线。若某结点X的左孩子结点存在,则将这个左孩子的右孩子结点、右孩子的右孩子的右孩子结点。。。都作为结点X的孩子。将结点X与这些右孩子结点用线连接起来。
(2)、去线。删除原二叉树中所有结点与其右孩子结点的连线。
(3)、层次调整。
4、二叉树转换为森林
前提: 假如一棵二叉树的根节点有右孩子,则这棵二叉树能够转换为森林,否则转换为一棵树。
转换规则:
(1)、从根节点开始,若右孩子存在,则把与右孩子结点的连线删除。再查看分离后的二叉树,若其根节点的右孩子存在,则连续删除。直到所有这些根结点与右孩子的连线都删除为止。
(2)、将每棵分离后的二叉树转换为树。
-
哈夫曼树
1.定义:哈夫曼树是一种带权路径长度最短的二叉树,也称为最优二叉树。
2.相关概念:
(1)路径长度:从树一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称作路径长度。
(2)树的路径长度:从树的根到每一个结点的路径长度之和。
(3)带权路径长度:树中所有叶子结点的带权路径长度之和。
3.构造大致步骤:
(1)将所有左,右子树都为空的作为根节点。
(2)在森林中选出两棵根节点的权值最小的树作为一棵新树的左,右子树,且置新树的附加根节点的权值为其左,右子树上根节点的权值之和。注意,左子树的权值应小于右子树的权值。
(3)从森林中删除这两棵树,同时把新树加入到森林中。
(4)重复2,3步骤,直到森林中只有一棵树为止,此树便是哈夫曼树。
4.哈夫曼编码
哈夫曼编码步骤:
(1)对给定的n个权值{W1,W2,W3,...,Wi,...,Wn}构成n棵二叉树的初始集合F= {T1,T2,T3,...,Ti,...,Tn},其中每棵二叉树Ti中只有一个权值为Wi的根结点,它的左右子树均为空。(为方便在计算机上实现算 法,一般还要求以Ti的权值Wi的升序排列。)
(2)在F中选取两棵根结点权值最小的树作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和。
(3)从F中删除这两棵树,并把这棵新的二叉树同样以升序排列加入到集合F中。
(4)重复二和三两步,直到集合F中只有一棵二叉树为止。
问题:
1.关于上面提到的的二叉树的建立,多种建立方法有很多一知半解,只明白最简单的根据先序遍历建立:
例如输入序列ABDH##I##E##CF#J##G##(#表示空),则会建立如下图所示的二叉树
BTree CreateBTree()
{
BTree bt = NULL;
char ch;
scanf("%c", &ch);
if (ch != '#')
{
bt = new BTNode;
bt->data = ch;
bt->lChild = CreateBTree();
bt->rChild = CreateBTree();
}
return bt;
}
关于利用中序遍历和后寻遍历建立二叉树则不太明白,主要是设计到递归函数的应用自己就容易变笨比。更甚至于给出不完全的三种遍历方法让你建立二叉树的问题更是一头雾水。太可怜了,呜呜呜。
附题目:
如果同时给出先序,中序,后序三种序列,但都是不完整的,能否建立一课二叉树呢?考虑下面一个例子:
一棵二叉树的先序、中序和后序序列分别如下,其中一部分未显示出来,试编程求出空格处的内容。
先序:_B_F_ICEH_G
中序:D_KFIA_EJC_
后序:_K_FBHJ_G_A
2.关于哈夫曼树,题目根本应用不好啊,虽然知道哈夫曼树的主要作用就是优化,但不明白具体要怎样“优化”?
附题目:
题目描述:
在一个果园里,小明已经将所有的水果打了下来,并按水果的不同种类分成了若干堆,小明决定把所有的水果合成一堆。每一次合并,小明可以把两堆水果合并到一起,消耗的体力等于两堆水果的重量之和。当然经过 n‐1 次合并之后,就变成一堆了。小明在合并水果时总共消耗的体力等于每次合并所耗体力之和。
假定每个水果重量都为 1,并且已知水果的种类数和每种水果的数目,你的任务是设计出合并的次序方案,使小明耗费的体力最少,并输出这个最小的体力耗费值。例如有 3 种水果,数目依次为 1,2,9。可以先将 1,2 堆合并,新堆数目为3,耗费体力为 3。然后将新堆与原先的第三堆合并得到新的堆,耗费体力为 12。所以小明总共耗费体力=3+12=15,可以证明 15 为最小的体力耗费值。
输入:
每组数据输入包括两行,第一行是一个整数 n(1<=n<=10000),表示水果的种类数,如果 n 等于 0 表示输入结束,且不用处理。第二行包含 n 个整数,用空格分隔,第 i 个整数(1<=ai<=1000)是第 i 种水果的数目。
输出:
对于每组输入,输出一个整数并换行,这个值也就是最小的体力耗费值。输入数据保证这个值小于 2^31。
样例输入:
3
9 1 2
0
样例输出:
15
(看答案都不懂的说,啊啊啊)