赞
踩
树是一种非线性的数据结构,它是有n个有限结点组成的一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根在上,叶在下的。
在树中有一个特殊的结点,称为根结点,根结点没有前驱结点
除根结点外,其余的结点被分成了M个互不相交的集合T1、T2、......、Tm,其中每一个集合Ti又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱结点,但可以有0个或多个后继结点。因此:树是递归定义的。
注意:树的结点之间是不可以有交集的,有交集的话就不是树了,至于是什么我们后期再说。
如下图:以A为根节点的就不是树,以D为结点的才是树。
我们在这里学习三种树的表示方法,分别为双亲表示法、树的孩子表示法、左孩子右兄弟表示法。
我们将用这三种方法来表示这一棵树:
树的父节点表示法,是利用顺序表来完成的,怎么做呢?
首先,我们创建顺序表结构体来存储结点的内容以及父节点的下标。我们将这个结构体称为结点结构体。
然后,我们创建树的结构体,其中一个是结点结构体数组,一个是数组内元素个数。
由此,我们可以写出如下代码:
- //双亲表示法
- //顺序表的方式存储
- //顺序表存储结点的数据和双亲结点的下标
- typedef int DataType;
- typedef struct Node
- {
- DataType data;//数据域
- int parent;//双亲结点下标
- }Node;
- //树-->结点组成的数组
- typedef struct Tree
- {
- Node NodeArr[10];//保存结点的数组,也可以动态申请
- int size;//结点个数
- };
在这里需要我们大家注意的是:由于根节点没有前驱结点,我们将其的父节点特别记为-1.
下面我们来表示一下这棵树。
树的孩子表示法是通过顺序表和链表结合的形式表示的
它的原理是:
那么,我们就可以写出如下代码:
- typedef int DataType;
- //链表
- struct ListNode
- {
- int child;//当前孩子结点下标
- struct Listnode* next;//下一个孩子
- };
- //顺序表
- struct Node
- {
- DataType data;//结点的数据
- ListNode* FirstChild;//第一个孩子
- };
- struct Tree
- {
- Node NodeArr[10];
- int size;
- };
现在我们画图来理解一下这个方法
这个方法的缺点就是:我们要在顺序表中插入或者删除数据需要移动大量的数据。
这个是最常用的表示树的的方法,即定义两个指针,让左指针指向最左边的子节点,右指针指向兄弟节点。
如果没有节点,则都指向空。
- typedef int DataType;
- struct Node
- {
- struct Node* leftChild;//孩子结点
- struct Node* rightBrother;//指向下一个兄弟结点
- DataType Data;//数据域
- };
我们下面具体画一下图:
在linux环境下目录结构就是有一颗树构成。
而在Windows环境下,目录许多内容并不交叉,所以是由森林构成。
一棵二叉树是结点的一个有限集合,该集合:
1.或者为空
2.由一个根节点加上两棵别称(别名/外号)为:左子树、右子树的二叉树组成
从上图可以看出
我们可以将一棵二叉树拆分开来,我们会发现任何一棵二叉树都是由以下几种情况复合而来的:
下面给大家看一张现实生活中存在的二叉树:
一个二叉树,如果每一层的节点数都达到了最大值,那么这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是2^k-1,则它就是满二叉树。
完全二叉树是一种效率很高的数据结构。
完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1到n的结点都可以一一对应时,则可称之为完全二叉树。也就是说:满二叉树是一种特殊的完全二叉树。(文字表述蛮抽象的,其实就是倒数第二层满,最后一层的叶子结点要从左往右放)
1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1)个结点.
2. 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是2^h-1.
3. 对任何一棵二叉树, 如果度为0其叶结点个数为 , 度为2的分支结点个数为 ,则有 n0=n2+1
4.若规定根结点的层数为1,具有n个结点的满二叉树的深度,h=log(n+1)
5.对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对
于序号为i的结点有:
1. 若i>0,i位置结点的双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子
结论3的推导过程:
其余结论的推导过程可参考:堆的实现
二叉树一般使用两种结构存储,一种顺序结构,一种链式结构。
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树。
因为不是完全二叉树会有空间的浪费。
而现实中使用中只有堆才会使用数组来存储,关于堆的实现可以参考上面的链接。
二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
如下图,左边是完全二叉树,不会有空间浪费;
右边是不完全二叉树,有空间浪费。
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。
如下图所示:
最左边是存储结构,中间是二叉树实例,右边是逻辑结构图。
在这里我们采用链式结构来存储二叉树:
二叉树的遍历方式有四种:前序遍历、中序遍历、后序遍历、层序遍历
二叉树的前、中、后序遍历都可以通过递归和用栈模拟递归实现,这里我们学习这两种方法.
大家可以先研究代码,理解困难的话可以根据视频学习:
视频1:
二叉树的前中后序遍历的递归实现
视频2:
二叉树前中后序遍历的非递归实现以及层序遍历
前序遍历:先遍历根节点,再依次遍历左子树,右子树。而遍历左子树,又要先遍历根节点,再依次遍历左子树,右子树…...直到遍历完整棵树。
递归实现
- void PreOrder1(BTree* root)
- {
- assert(root);
- if (!root)
- {
- return;
- }
- printf("%d ", root->data);
- PreOrder(root->left);
- PreOrder(root->right);
- }
非递归实现
- typedef BTree* STDataType;
- void PreOrder2(BTree* root)
- {
- //开辟一个栈
- Stack s;
- StackInit(&s);
- BTree* p = root;
- while (p || !IsEmpty(&s))
- {
- if (p != NULL)
- {
- printf("%d ", p->data);
- StackPush(&s, p);
- p = p->left;
- }
- else
- {
- p = StackTop(&s);
- StackPop(&s);
- p = p->right;
- }
- }
- }
先遍历左子树,再依次遍历根节点,右子树。而遍历左子树,又要先遍历左子树,再依次遍历根节点,右子树…直至遍历完整棵树。
递归实现
- void Inorder1(BTree*root)
- {
- if (root == NULL)
- {
- return;
- }
- PreOrder(root->left);//左子树
- printf("%d ", root->data);//根节点
- PreOrder(root->right);//右子树
- }
非递归实现
- typedef BTree* STDataType;
- void Inorder2(BTree* root)
- {
- Stack s;
- InitStack(&s);
- BTree* p = root;
- while (p || !IsEmpty(&s))
- {
- if (p != NULL)//入栈
- {
- StackPush(&s, p);
- p = p->left;
- }
- else
- {
- p = StackTop(&s);
- StackPop(&s);
- printf("%d ", p->data);
- p = p->right;
- }
- }
- }
后序遍历:先遍历左子树,再依次遍历右子树,根节点。而遍历左子树,又要先遍历左子树,再依次遍历右子树,根节点…直至遍历完整棵树。
递归实现
- void Postorder1(BTree*root)
- {
- if (root == NULL)
- {
- return;
- }
- PreOrder(root->left);//左子树
- PreOrder(root->right);//右子树
- printf("%d ", root->data);//根节点
- }
非递归实现
- void Postorder2(BTree* root)
- {
- Stack s;
- InitStack(&s);
- BTree* p = root;// p为遍历指针
- BTree* v = root;// v标记已访问节点
- while (p || !IsEmpty(&s)) // 栈不为空或p不为空时循环
- {
- while(p != NULL)//入栈
- {
- StackPush(&s, p);
- p = p->left;
- }
- p = StackTop(&s);
- if (p->right && p->right != v)//存在右子树,且没有被访问
- {
- p = p->right;//访问
- }
- else//没有右子树或者右子树已被访问
- {
- printf("%d ", p->data);
- v = p;//记录当前访问的节点
- p = NULL;//防止重复访问左子树
- StackPop(&s);// 栈顶元素出栈
- }
- }
- }
层序遍历,就是一层一层的遍历。
这里我们借助队列来实现。
- void leverOrder(BTree* root, Queue* pq)
- {
- if (root == NULL)//为空直接返回
- {
- return;
- }
- QueuePush(pq, root);//插入第一个节点
- while (!QueueEmpty(pq))//队列不为空
- {
- BTree* p = QueueFront(pq);
- printf("%d ", p->data);
- QueuePop(pq);
- if (p->left != NULL)//带入左孩子
- {
- QueuePush(pq, p->left);
- }
- if (p->right != NULL)//带入右孩子
- {
- QueuePush(pq, p->right);
- }
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。