先推荐一下书吧,之前用的一直是清华大学出版社的数据结构,感觉大学教材更多的也是这本。但是觉得这本书有些东西写的过于啰嗦,可能对新手更友好一点。而且他是C语言描述的,不是完全用C语言写,很多程序还要自己更正一下,觉得看起来不是太舒服。现在用的书是Mark Allen写的数据结构与算法分析,感觉蛮适合我的,但是最好是对指针里链表很熟悉了再来看这本书,不然会一脸懵。对于书的选择,还是看个人爱好吧,不管哪本书,只要学透了肯定都是没问题的。好了,现在开始树了。
首先先了解一下为什么要有树这种数据结构。对于大量的输入数据,链表的线性访问时间太慢,而通过树来访问链表,大部分操作的运行平均时间是O(logN)。
树的定义:树是一类重要的非线性数据结构,是以分支关系定义的层次结构。树是n个节点的有限集,其中有且只有一个根节点。当n>1时,其他节点可分为互不相交的有限集,其中每个集合本身又是一棵树,称为子树。非空树中各子树是互不相交的集合。结合图看,A就是根结点,B C 及下面的是A的子树,B C之间互不相交。树中有一些定义:
结点:表示树中数据元素,包括数据项和若干指向其他结点的指针。
结点的度:结点拥有的子树数,结点A的度是2
叶子:度为0的结点,图中的#都是叶子,他们没有子树。
孩子:结点子树的跟称为该结点的孩子,B和C是A的孩子,但D不是,D是B的孩子
双亲:孩子的上层结点就是双亲(虽然叫双亲,但只有一个),D的双亲是B
兄弟:同一双亲的孩子,B和C就是兄弟
数的度:一棵树中最大的结点度数,图中所有结点度都是2,所以数的度也是2
结点的层次:从根节点算起,根A为第一层,它的孩子B.C为第二层。。。。
深度:树中结点的最大层次数,图中深度为4
实现树的一种方式是在每一个结点除了数据外还有一些指针,使得每个结点都可以从某个结点找到他。常用的方式有双亲表示法、孩子表示法,孩子兄弟表示法。拿双亲表示法举例子,孩子表示法就是有指向孩子的指针,有几个指针就有几个指针指向每个孩子,A有两个指针,一个指向B一个指向C。其他的几种表示法都是通过字面意思就可以理解的。
树有很多应用,比如操作系统的目录结构,等等。树还有另一种特殊形式,叫二叉树。
二叉树定义:二叉树和树一样,也是N个结点的有限集,不同之处是它或为空数(N=0),或由一个根结点和两棵分别称为左子树和右子树的互不相交的二叉树构成。简单点说就是,二叉树最多只能有两个孩子,一个叫左孩子,一个叫右孩子。上面的图其实就是一个二叉树。二叉树有几个特殊的性质:
1.在二叉树的第n层上最多有2^(n-1)个结点,第一层最多一个,第二层最多两个,以此类推
2.深度位k的二叉树上至多含2^k-1个结点,证明用等比数列求和公式就证出来了.
3.对任意一个二叉树,如果其叶子节点数为n0,度为2的结点数为n1,则n0=n2+1
4.具有n个完全二叉树的深度为log2(n)+1(完全二叉树就是按照先左孩子后右孩子,再兄弟的左孩子,兄弟的右孩子,都没有再下一层的按顺序的二叉树)
二叉查找树:二叉树的一个重要应用是在查找中使用,二叉查找树的特点是对于树中的每个节点,他的左子树全部小于他,他的右子树全部大于他。由于树的递归定义,通常写树的时候都用递归方式来写。递归是以牺牲堆栈空间来简化代码,由于二叉树的平均深度是O(log2(n)),所以我们不用担心栈呗耗尽。(用递归来写程序要思路非常清晰才可以 不然就懵了,建议大家练习的时候先自己想,再去看标准答案,不然思路会被引导的)
下面就是程序练习了,需要实现以下几个功能:
1.二叉查找树的添加结点程序Insert(初始化就不用写了,当空树时直接在Insert里面申请空间),需要注意的是比结点大,放到右子树,小放到左子树。
2.二叉查找树的查找某数据程序Find,返回该结点的结构体指针
3.二叉查找树的找最大结点程序FindMax,其实就是一直找右子树,找到空为止
4.二叉查找树的找最小结点程序FindMin,一直找左子树,找到空为止
5.二叉查找树的遍历程序,遍历有三种:先序、中序、后序,这里的先中后代表的是根节点的先中后。因为是查找树,所以我们用中序遍历,这样遍历出来的是有顺序的
6.二叉查找树的删除某结点程序Delete,这个程序是最难的,需要好好好好想一想。因为删除不只是删除结点那么简单,你需要删除结点后还保持查找二叉树的属性。如果要删的结点只有一个孩子或者没有孩子,那还比较简单,直接指向他的指针指向他的唯一孩子就好了。但是如果有两个孩子,删完该怎么恢复二叉查找树,大家先想想,想不出来再看我的程序。
下面上程序了:
#include<stdio.h>//printf函数
#include<stdlib.h>//malloc函数
/*查找二叉树,左孩子小,右孩子大*/
typedef struct Tree* TreeNode;
struct Tree
{
TreeNode left;
TreeNode right;
int elem;
};
/*既可以加节点,也可以建立节点,通过递归实现*/
TreeNode Insert(int elem,TreeNode treeNode)
{
if(treeNode == NULL)//空树
{
treeNode = (TreeNode)malloc(sizeof(struct Tree));
if(treeNode == NULL)
printf("Out of space");
else
{
treeNode->elem = elem;
treeNode->left = treeNode->right = NULL;
}
}
else//小加在左子树,大于加在右子树
{
if(elem<treeNode->elem)
treeNode->left = Insert(elem,treeNode->left);
else if(elem>treeNode->elem)
treeNode->right = Insert(elem,treeNode->right);
}
return treeNode;
}
/*找最小的节点 只要一直找左孩子就可以了 使用递归实现*/
TreeNode FindMin(TreeNode treeNode)
{
if(treeNode == NULL)
printf("Find failure");
else if(treeNode->left)
FindMin(treeNode->left);
else
return treeNode;
}
/*找最大的节点 只要一直找右孩子就可以了 使用非递归实现*/
TreeNode FindMax(TreeNode treeNode)
{
if(treeNode == NULL)
printf("Find failure");
else
{
while(treeNode->right)
treeNode = treeNode->right;
}
return treeNode;
}
/*删除必须先查找 根据查到的位置信息进行*/
TreeNode Delete(int elem,TreeNode treeNode)
{
TreeNode posotion = NULL;//用来释放节点
if(treeNode == NULL)
printf("Find nfailure");
else
{
if(elem<treeNode->elem)
treeNode->left = Delete(elem,treeNode->left);
else if(elem>treeNode->elem)
treeNode->right = Delete(elem,treeNode->right);
else//要判断有几个孩子
{
if(treeNode->left && treeNode->right)//两个孩子 特殊
{
treeNode->elem = FindMin(treeNode->right)->elem;
treeNode->right = Delete(elem,treeNode->right);
}
else
{
posotion = treeNode;
if(treeNode->left)
treeNode = treeNode->left;
else
treeNode = treeNode->right;
free(posotion);
}
}
}
return treeNode;
}
TreeNode Find(int elem,TreeNode treeNode)
{
if(treeNode == NULL)
printf("Find failure");
if(elem<treeNode->elem)
return Find(elem,treeNode->left);
else if(elem>treeNode->elem)
return Find(elem,treeNode->right);
else
return treeNode;
}
void InOrderTraverse(TreeNode treeNode)
{
if(treeNode == NULL)
return;
InOrderTraverse(treeNode->left);
printf("%d",treeNode->elem);//先序遍历后续遍历只要把这句话放前面或者后面就好了
InOrderTraverse(treeNode->right);
}
int main()
{
int i = 0;
TreeNode rootNode = NULL;
TreeNode position = NULL;
rootNode = Insert(5,rootNode);
rootNode = Insert(4,rootNode);
for(i=1;i<11;i++)
rootNode = Insert(i,rootNode);
//position = FindMax(rootNode);
//Delete(4,rootNode);
InOrderTraverse(rootNode);
}
对于删除有两个孩子的结点的操作是:现在他的右子树里面找到最小的,用数据去替换该结点数据。然后在二次查找,把那个右子树里面最小的结点给删了。
PS:使用递归写程序对于刚接触递归的人真是一个小挑战,你需要让自己的思维打破原来,去接受一种新的模式。还是很有意思的,大家可以自己练习一下~