数据结构04-树
4:树(QUEUE)
4.1:树的定义和性质
- 树是一种重要的非线性数据结构;
- 树是由一个或多个结点组成的有限集合;
树必有一个特定的称为根(ROOT)的结点;
剩下的结点被分成n>=0个互不相交的集合T1、T2、......Tn,而且, 这些集合的每一个又都是树。树T1、T2、......Tn被称作根的子树(Subtree)。
树的递归定义如下:
1. 至少有一个结点(称为根)
2. 其它是互不相交的子树。
树的度:结点的最大分支数。以组成该树各结点中最大的度作为该树的度;
树中度为零的结点称为叶结点或终端结点。树中度不为零的结点称为分枝结点或非终端结点。除根结点外的分枝结点统称为内部结点。
树的深度:组成该树各结点的最大层次,从1开始计数;
有序树:指树中同层结点从左到右有次序排列,它们之间的次序不能互换,这样的树称为有序树,否则称为无序树。
二叉树:每个节点至多只有两棵子树,度不能大于2,并且二叉树的子树有左右之分,左右次序不能改变。
二叉树五种基本形态(如上图 依次分别为):
- 空二叉树
- 只有一个根节点的二叉树
- 只有左子树
- 只有右子树
- 完全二叉树
- 满二叉树:一颗深度为K且有2^(k-1)个结点的二叉树.
- 完全二叉树:对二叉树的结点进行连续编号,约定编号从根节点起,自上而下,自左至右。深度为k,有n个结点的二叉树,当且仅当其每一个结点都与深度k的满二叉树中编号从1到n的结点一一对应
二叉树的第i层上至多有2^i-1个结点(i>=1)
深度为k的二叉树至多有2^k-1个结点(k>=1)
具有n个结点的完全二叉树的深度为:
4.1:树的创建
//二叉树存储结构
typedef struct _treeNode {
int value;
struct _treeNode *left;
struct _treeNode *right;
} tree, *pTree;
//二叉树的创建,递归创建
tree *creat_tree() {
int value = 0;
scanf("%d", &value);
if (value == 0)//输入零,表示左或右子树为空
return NULL;
tree *node = (tree *)malloc(sizeof(tree));
if (node == NULL)
return NULL;
memset(node, 0, sizeof(tree));
node->value = value;
//递归创建左右子树
printf("Please create the left tree of %d\n",value);
node->left = create_tree();
printf("Please create the right tree of %d\n",value);
node->right = create_tree();
return node;
}
4.2:树的遍历
遍历:对树的一种最基本的运算(二叉树是非线性结构,但在遍历时是按一定规则和顺序走遍二叉树的所有结点,使每一个结点都被访问一次,而且只被访问一次)。
树的遍历实质上是将二叉树的各个结点转换成为一个线性序列来表示。
- L:遍历左子树
- D:访问根结点
- R:遍历右子树
对一棵二叉树遍历分三种情况(下面先序,中序和后序都是以根为标准):
- DLR(称为先根次序遍历,即先序遍历)
- LDR(称为中根次序遍历,即中序遍历)
- LRD(称为后根次序遍历,即后序遍历)
4.2.1:树的先序遍历
先序遍历的方法:访问根;按先序遍历左子树;按先序遍历右子树。
//先序遍历的递归算法
void preorder(tree *node) {
if(node == NULL)
return;
printf(“%d”, node->data);//7,3,1,4,6,8,5,2
preorder(node->left);
preorder(node->right);
}
4.2.2:树的中序遍历
中序遍历的方法:按中序遍历左子树;访问根;按中序遍历右子树。
//中序遍历
void inorder(tree *node) {
if(node == NULL)
return;
inorder(node->left);
printf(“%d”, node->data);//1,3,6,4,7,5,8,2
inorder(node->right);
}
4.2.3:树的后序遍历
后序遍历的方法:按后序遍历左子树;按后序遍历右子树;访问根。
void postorder(tree *node) {
if(node == NULL)
return;
postorder(t->left);
postorder(t->right);
printf(“%d”, t->data);//1,6,4,3,5,2,8,7
}
4.3:树的销毁
vode pre_destroy(tree *node) {
if (node == NULL)
return NULL;
tree *left = node->left;
tree *right = node->right;
free(node);
pre_destroy(left);
pre_destroy(right);
}
4.4:平衡二叉树(AVL)/红黑树(rbtree)/B+树/B-树
- 二叉排序树:所有非叶子结点至多拥有两个儿子(Left和Right);所有结点存储一个关键字;非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树;
- 平衡二叉排序树:一种改进的二叉排序树,一棵平衡二叉排序树或者是一棵空树,或者是一棵任意一结点的左子树与右子树的高度至多差1的二叉树排序树。对于二叉排序树上的任何结点,其平衡因子定义为该结点左子树的高度减去该结点右子树高度。任一结点的平衡因子只可能是-1,0,1。平衡的二叉排序树的查找方法与一般的二叉排序树完全一样,其优点是总能保持查找长度为O(log2n)量级。往平衡的二叉排序树插入新结点时,需要对树的结构进行必要调整,以动态地保持平衡二叉排序树的特点。
- 红黑树(RB Tree):是平衡二叉树,查找的效率也就一样,为logN。所以在C++的STL库中,set/map,multiset/multimap就是用的红黑树作为底层的数据结构,方便查找与插入删除操作。
红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树增加了如下的额外要求:
性质1: 节点是红色或黑色。
性质2: 根是黑色。
性质3: 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质4: 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。
B树:即二叉排序(搜索)树
- 所有非叶子结点至多拥有两个儿子(Left和Right);
- 所有结点存储一个关键字;
-
非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树;
B-树:一种多路搜索树(并不是二叉的)
- 定义任意非叶子结点最多只有M个儿子;且M>2;
- 根结点的儿子数为[2, M];
- 除根结点以外的非叶子结点的儿子数为[M/2, M];
- 每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)
- 非叶子结点的关键字个数=指向儿子的指针个数-1;
- 非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
- 非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;
- 所有叶子结点位于同一层;
B+树:是B-树的变体,也是一种多路搜索树:
- 其定义基本与B-树同,除了:
- 非叶子结点的子树指针与关键字个数相同;
- 非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间);
- 为所有叶子结点增加一个链指针;
- 所有关键字都在叶子结点出现