数据结构------树

来源:互联网 发布:虚拟服务器和端口转发 编辑:程序博客网 时间:2024/06/06 21:00

数据结构----------------树

前序:

什么是树呢?树是以递归的方式进行定义,树是有且仅有一个根结点,其余各结点构成互不相交的子树。

其实学习“树”个人认为需要首先学习“递归”。当然不必深入的学习,需要把基本的例子能用代码实现,如:求前N项和,斐波那契数列,这些小例子。理解递归中函数的调用原理。

递归:一个函数直接的或者间接的调用自己。

要想明白递归是怎样调用的就需要了解函数之间的调用过程。(参考严蔚敏老师的数据结构p56最上面的一段话)

当一个函数在运行期间调用另外一个函数时,在运行被调用函数之前,系统须先完成3件事:1.将所有的实参、返回地址(调用函数执行完的下一个语句的地址)发给被调函数,被调函数保存。2、为被调函数的局部变量分内存。3、控制权限转交给被调函数,到被调函数的入口执行代码(刚开始程序的执行权在主调函数)。

释放回调函数同样3件事。1、返回值保存。2、释放被调函数使用过程使用的内存(当然只包括静态内存)。3、控制权限回给主调函数。程序从刚才保存的地址开始执行。

其实刚才我们就用到的是递归。比如说学习是一个函数,“树”,”递归“,”函数执行过程”是参数,那么我们就可以看成是
               学习(知识点)

                    {

             if(知识点==函数执行过程)

             {

                       ok

              }

             知识点=知识点->next;

             学习(知识点)

              }

当然不是很准确,大概是这个意思。

下面开始讨论树。

一、树定义

                 1、有且仅有一个称作根的结点

                 2、有若干个互不相交的子树,这些子树本身也是一个树。

                        树借助规模更小的子树来定义。

二、名词解释

父节点、子节点、堂兄弟、兄弟这些都是相对的概念。这些概念不会有太大的问题。

叶子结点:没有子节点的结点。

结点的度:该结点的子节点的个数。

树的度:所有节点中度最大的那个就是树的的度。

深度:就是从根节点到最底层的叶子结点有多少行。

这里只要注意父节点、子结点和子孙的概念,直接相连的才是子节点、父节点,不直接相连的是子孙。

二叉树:任意节点的子结点个数不大于2。且子结点的顺序不能变。

满二叉树:再添加一个结点只能在下一层进行添加的二叉树就叫做满二叉树。不增加层的前提下不能添加新的结点。

完全二叉树:只删除了满二叉树的最底层最右边的连续若干结点的二叉树叫做完全二叉树。满二叉树是完全二叉树的一个特例。

森林:N个互不相交的树的集合。

三、二叉树的5个性质

            1、二叉树第K层最多有2^(k-1)个结点。(1--2--4--8--16道理,一个叉分两个的原因)

            2、二叉树有k层结点最多为2^k-1。这个就是(Sn=1+2+3+……的道理)

            3、二叉树的叶子节点和度数为2的结点关系:N0=N2+1;

            4、完全二叉树总结点个数为N,则层数[log2^n]+1。

            5、5.1完全二叉树中某个结点(序号i)的父节点i/2。小于0不存在。

                  5.2完全二叉树中某个结点(序号i)的左孩子2*i,2*i>n则i是叶子节点

                  5.3完全二叉树中某个结点(序号i)的右孩子为2*i+1,2*i+1>n则不存在右孩子。

四、树的存储

链表、数组在逻辑上都是线性的,内存也是线性的。因此无论是怎样都可以从头开始到尾结束。但是树不同,逻辑上它是层次关系,而不是先后顺序 。

我们要回到数据结构的初衷,研究的是数据的存储。数据的存储包括,数据元素和数据元素的关系。如果只存数值,那么就不知道他们之间的关系了。(表达的不是很好)我对数据结构的理解就是把逻辑关系在物理内存中表达。

因此树基于连续存储和链式存储就会出现很多种存储方法:

双亲表示法:因为一个结点只有一个双亲的思想。

孩子表示法:数据用数组表示,孩子用链表表示,这样查找孩子块了很多。

孩子双亲表示:两个结合。

孩子兄弟表示法:结点指针域指向最左边第一个孩子,右指针域指向节点的兄弟。(这也是树、森林转二叉树的方法)

树的存储同样有两种结构,。

对于连续存储这样的结构,树必须转换成完全二叉树来存储。这样虽然浪费了很多内存空间,但是可以还原出原来二叉树的原貌。数据和关系都存进去了。

五、树、森林转换成二叉树

        1、树转换成二叉树大部分书上都写的蛮复杂,总结一下就是一句话:从根结点开始,结点的左指针域指向结点的左孩子,右结点指向该结点的有兄弟。

        2、森林转换成二叉树也是两句话:1、第一棵树的的根节点当作转换后的二叉树的根节点,第二个棵树的根节点当作第一棵树的右兄弟,第三棵树当作第二颗树的右兄弟节点。这样森林就是一个普通树。2、按照树转二叉树进行即可。

六、二叉树的链式存储及其操作

这里只讨论最基本的树。即只有 data 、lchild、rchild(数据域,左指针域,右指针域)。

结点的数据类型:

typdedef struct tNode

{

              elemtype data;

              struct tNode *plchild;

              struct tNode *prchild;

}tNode,*pTree;

结点初始化方式三种,先序、中序、后序。这里的先中后是按照方位根节点的顺序定义的。即

先序初始化顺序:根节点--------左子树----------右子树

中序初始化顺序:左子树--------根节点----------右子树

后序初始化顺序:左子树--------右子树----------根节点

         伪代码如下:

        pTree create_tree()

                新建一个根节点 root

                如果没有新建成功证明内存不够,或者其他问题,直接报错退出程序

                如果新建成功那么就给结点赋值

                再调用create_tree方法,创建该根节点的左子树。

                再调用create_tree方法,创建该根节点的右子树。

注:伪代码是按照先序赋值,变换次序即是按中序、后序赋值。

 树的遍历:有了初始化遍历就简单了很多(只要知道根节点即可)

                 伪代码如下:

                      void traverse_tree(pTree root)

                           如果根节点为空就结束函数

                           不为空就输出该节点的值

                           前序遍历左孩子

                           前序遍历右孩子

注:伪代码是按照先序遍历,变换次序即是按中序、后序遍历。

如果还是理解不了这里,我想很可能是没有理解递归的含义,花上半天或者2、3个小时把递归的的知识看一下就可以了,再回头看树的赋值和遍历就会很简单,也会有不同的理解。其实本人对这里也有疑惑(个人感觉数据结构是一个长期学习的课程)。

关于赋值和遍历的具体代码附加在后面。

七、先序+中序——>后序和中序+后序——>前序

这里需要掌握先序中序和后序的遍历或者赋值的顺序,或者说他们的内核是什么。

先序的内核:最先方位的肯定是根节点,然后是左子树根节点,右子树根节点。

中序的内核:最先访问的肯定是左子树的根节点,然后是根节点,最后是右子树根节点

后序的内核:最先方位左子树根节点,右子树根节点,然后是根节点

他们可以确定什么?先序可以确定那个根节点,中序可以确定左子树,右子树(先序中根节点的左边就是左子树,右边就是右子树),后序可以确定什么?同先序一样可以确定根节点,只是从后向前确定。

因此先序+中序确定后序的伪代码:

先序队列找根节点

中序队列找左子树

中序队列找右子树。

八、总结:

          研究树其实就是为了应用。树的应用非常广泛。解压缩文件、数据库等等,很多,谷歌、百度上有很多。为什么我们研究树呢?因为现实生活中的数据关系并不是内存的线性结构,而是非线性结构,如果仅仅是存储了数据,没有存储他们的关系,那么就是失去了研究数据结构本身的意义了。

         树最重要的是要掌握树的遍历和树两种顺序退出第三种顺序。排序还没有学习,后期补上。

/*demo_tree.cpp------------树的创建遍历*/
#include <stdio.h>
#include <malloc.h>

typedef struct tNode
{
 char data;
 struct tNode *pLchild;
 struct tNode *pRchild;
}tNode,* pTree;
pTree create_tree();
void traverse_tree(pTree tree);

int main(void)
{
 printf("请先序(中序、后序自行修改)输入结点的值,这里用字符\n");
 pTree root=create_tree();
 printf("先序遍历\n");
 traverse_tree(root);
 return 0;
}
pTree create_tree()
{
 char value;
 pTree root=(pTree)malloc(sizeof(tNode));


 if(NULL==root)
 {
  printf("动态内存分配有问题\n");
  return NULL;
 }
 scanf("%c",&value);
 if('*'==value)//这里用"*"表示空指针用其他字符也是一样的。输入的时候NULL输成“*”即可。
 {
  root=NULL;
 }
 else
 {
  //这里是先序赋值。改变顺序即是中序、后序赋值
  root->data=value;
  root->pLchild=create_tree();
  root->pRchild=create_tree();
 }

 return root;
}
void traverse_tree(pTree root)
{
 if(NULL==root)
 {
  return;
 }
 else
 {
  printf("%c ",root->data);
  traverse_tree(root->pLchild);
  traverse_tree(root->pRchild);
 }
}

输入:abd***ce**fg***的样式,先序输出:a b d c e f g

 

0 0