数据结构分为 线性结构和非线性结构,树是一种非线性结构。
1.树的基本概念
1.树的定义:
形式化定义:(二编组,结点组D和边组R)
树:T={D,R}。D是包含n个节点的有限集合(n>=0)。当n=0时为空树,否则关系R满足以下条件:
- 有且有一个结点,对于关系R来说没有前驱结点,结点称作根结点。
- 除了根节点外,每个结点有且仅有一个前驱结点。
- D中每个结点可以有0个或多个后继结点。
递归定义:
树是由n(n>=0)个结点组组成的有限集合(记为T)。其中:
- 如果n=0,它是一棵空树,这是树的特例
- 如果n>0,这n个结点中存在一个唯一的节点作为树的根结点。
- 其余节点,本身又是一颗树。称为根节点root的子树。
- 树中所有节点构成一种层次关系
特性描述定义:
- 树是一个无环的,无向连同图
- 有n个结点,和n-1条边
- 有一个根节点,通常需要一个引用
- 任意点条连接,且只有条路径
2.常用术语
结点的度和数的度:节点的度,节点拥有的子树的个数;树的度,树中所有节点的度,中的最大值。
分支节点与叶子结点:度不为零的结点称为非终端结点(又叫分支节点)。度为0的节点称为叶节点(叶子节点)。
路径:两个节点,和连接这两个节点的结点序列,称为这两个结点之间的路径。
路径长度:路径中结点的数目称为路径长度减1,(也就是分支数目)
根节点:没有父节点,有的实现中,根节点的父节点,指向自己。
叶子节点:没有子节点的节点。(度为0的节点)
非叶子节点:度不为0的节点。
子树:包含再一棵树中的小数 用表示
父节点:直接相连的两个节点,上面的叫父节点。
子节点:下面的叫子节点。
节点的层次和数的高度:根结点在1层,它孩子节点再2层,以此类推;树中结点的最大层称为树的高度。
有序树和无序树:若树中各结点的子树是按照一定的次序从左向右安排的,且相对次序不能随意变换的,则称为有序树,否则称为无序树。
森林:n(n>0)个互不相交的数的集合称为森林。
只要把树的根节点删除,就成了森林。
反正,只要给n棵独立的数加上一个节点,并吧这n棵树作为该节点的子树,则森林就变成了一棵树。
2.二叉树
二叉树不是一种特殊的树,和树是平行的概念。当二叉树只有一个子结点时,也有左节点和右结点之分。而树,没有这样的区别。
1.定义
- 二叉树是有限的结点集合
- 这个集合可能为空
- 或者由一个根节点和两棵互不相交的称为左子树和右子树的二叉树组成,
二叉树有5种基本形态
二叉树和度为二的树,是两种完全不同的结构(左右子树)
2.两种特殊的二叉树
1.满二叉树:
- 如果所有的结点都是双分支节点
- 并且叶节点集中再二叉树的最下一层。
可以按层遍历,并且给每个节点编号。
2.完全二叉树:
- 最多只有下面两层的结点的度小于2
- 并且最下面一层的叶节点都依次排在最左边的位置上
- 完全二叉树实际上是对应的满二叉树删除叶子节点最左边的若干个结点得到的
3.二叉树的性质
(1).
- 度之和=分支数
- 分支数=n -1
- 度之和=
(2) 非空二叉树上第i层上至多有个结点
(3) 高度为h的二叉树最多有个结点
(4) 完全二叉树有:
- 0或1,可以由n的奇偶行确定。
- 若 i <=n/2,则编号为i的结点为分支结点,否则为叶结点。
- 除树根结点外,若一个结点的编号为i,则它的双亲结点的编号为i/2
- 若编号为i的结点有左孩子结点,则左孩子结点的编号为2i;若编号为i的结点,有右孩子则结点编号为2i+1
- 具有n个结点的完全二叉树的深度为
3.二叉树和树与森林的转化。
1.森林树转化为二叉树
- 连线:所有兄弟连续
- 删线:只保留父结点和第一个孩子的连线
- 旋转:原来的孩子结点为左孩子,原来的兄
这样转化的话,根结点是没有右子树的,对于图,依次连接根结点和做子树,就能把图转化为二叉树。
2.二叉树转化为森林或树
- 连线:左子树的右孩子,意见右孩子的右孩子,的右孩子。。。连在一起。
- 删除:所有的右孩子线,删除
- 旋转:逆时针旋转
如果没有右子树转化成树,如果有右子树,转化成森林。
4.二叉树的存储结构
typedef struct BTNode{
void* data
struct BTNode * leftChild
struct BTNode * rightChild
}BTNode;
5.二叉树的遍历算法
二叉树的遍历分为三种:先序遍历、中序遍历、后续遍历、层序遍历(一般是完全二叉树)
1.先序遍历:所谓的前序遍历就是先访问根节点,再访问左节点,最后访问右节点
如上图所示,前序遍历结果为:ABDFECGHI
伪代码:
//tree待遍历二叉树的根结点
递归实现:
PreOrderTraverse(tree) {
visited(tree.Date)
PreOrderTraverse(root.leftChild)
PreOrderTraverse(root.rightChild)
}
非递归实现:
1.首先申请一个新的栈,记为stack;
2.声明一个结点treeNode,让其指向node结点;
3.如果treeNode的不为空,将treeNode的值打印,并将treeNode入栈,然后让treeNode指向treeNode的左孩子
4.重复步骤3,直到treenode为空;
5.然后出栈,让treeNode指向treeNode的右孩子
6.重复步骤3,直到stack为空.
PreOrderTraverse(tree) {
let S be a stack
travalNode = tree
while travalNode is not empty {
visited(travalNode.data)
stack.push(travalNode )
travalNode = travalNode.leftChild
while travalNode is empty && !s.Empty(){
travalNode = S.push
}
}
}
go语言实现:datestruct: 数据结构go语言实现 - Gitee.com
2.中序遍历:所谓的中序遍历就是先访问左节点,再访问根节点,最后访问右节点
如上图所示,中序遍历结果为:DBEFAGHCI
伪代码实现:
//tree待遍历二叉树的根结点
递归实现:
MidOrderTraverse(tree) {
MidOrderTraverse(root.leftChild)
visited(tree.Date) //访问逻辑
MidOrderTraverse(root.rightChild)
}
非递归实现:
//和前序的不同是,出栈时打印。
MidOrderTraverse(tree) {
let S be a Stack
cur = tree
while cur is not empty {
S.push(cur)
cur = cur.leftChild
while S is not empyt cur is empty { //访问并入栈右孩子,右孩子从头开始上面循环
cur = S.pop()
visited()
cur = cur.rightChild
}
}
}
go语言实现:datestruct: 数据结构go语言实现 - Gitee.com
注意:非递归实现中,前序遍历和中序遍历的代码结构是一样的,只不过前序是结点入栈前访问,中序是出栈后打印。
3.后序遍历:所谓的后序遍历就是先访问左节点,再访问右节点,最后访问根节点。
如上图所示,后序遍历结果为:DEFBHGICA
伪代码实现 :
//tree待遍历二叉树的根结点
AfterOrderTraverse(tree) {
AfterOrderTraverse(root.leftChild)
AfterOrderTrave(root.rightChild)
visited(tree.Date) //访问逻辑
}
go语言实现:datestruct: 数据结构go语言实现 - Gitee.com