树
前言:以下所有概念来自教材、算法导论或其他权威资料,如有记录出错,欢迎指正
定义
- 树是一种非线性的数据结构
- 树是若干个结点的集合(个数>=0),是由唯一的根和若干棵互不相交的子树组成
- 树的结点树可以为0,对于这种树,我们称为空树
- 树与图的区别在于树中没有一个闭环
基本术语
- 结点:组成树的元素,结点包含数据元素和指向子树的分支
- 结点的度:结点分支数
- 树的度:结点拥有的最大分支数
- 叶子结点:度为0的结点
- 非叶子结点:度不为0的结点,除去根节点的非叶子结点也称为内部结点
二叉树
- 定义:即每个结点都最多只有两个子结点的树
- 完全二叉树:高度为k的二叉树,其1~h-1层为满结点,且其h层(叶子结点层)的节点从左至右依次排列(最多2^h-1个,最少0个)
- 满二叉树:除最后一层外,每个结点都有左右子结点的二叉树
- 平衡二叉树:任一结点的左右子树的高度差绝对值不超过1,且左右子树均为平衡二叉树(防止树退化成链表)
相关算法
二叉树遍历
结点本身N,结点左子树L,结点右子树R
- 先序遍历:NLR
- 中序遍历:LNR
- 后序遍历:LRN
以先序遍历二叉树为例
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
}
/**
* 递归方式
*/
void preOrder1(TreeNode* tree)
{
if (tree) {
cout<<tree->val;
preOrder1(tree->left);
preOrder1(tree->right);
}
}
/**
* 非递归调用
* 使用栈来存储遍历过的结点
*/
void preOrder2(TreeNode* tree)
{
if (tree == NULL)
cout<<"";
stack<TreeNode*> s;
while (tree || !s.empty()) {
while (tree) {
cout<<tree->val;
s.push(tree);
tree = tree->left;
}
tree = s.top();
s.pop();
tree = tree->right;
}
}
二叉树交换左右子树
也有递归和非递归两种实现方式,非递归利用队列来实现
/**
* 递归写法
*/
void changeNode(TreeNode* tree)
{
if (tree == NULL) {
return;
}
TreeNode* temp = tree->right;
tree->right = tree->left;
tree->left = temp;
changeNode(tree->left);
changeNode(tree->right);
}
/**
* 非递归
* 用队列来实现
*/
void changeNode(TreeNode* tree)
{
queue<TreeNode*> q;//使用队列来记录访问的结点
TreeNode* temp;
int first = 0,last = 0;//指针来记录访问结点和交换结点
q[first++] = root;
while (first != last) {
tree = queue[last++];
temp = tree->left;
tree->left = tree->right;
tree->right = temp;
if (tree->left != NULL) {
queue[first++] = tree->left;
}
if (tree->right != NULL) {
queue[first++] = tree->right;
}
}
}
B-Tree
- 先说明下,数据结构中有的是B-Tree和B+Tree这两种,很多人误解成有B-(减)树,B树和B+(加)树,这是错误的!!!百度搜索出来的前几篇就有这个错误,很容易误导人。
- 本质:平衡m阶查找树
- 概念: 1. B树每个节点有K(K>0)个关键字(根节点除外,根节点没有关键字时树为空),结点的分支数等于关键字数+1,最大的分支数就是B树的阶数,因此m阶的B树中结点最多有m个分支
- 非根节点和叶子节点的其他节点,分支数 > (阶数 / 2),若5阶B树则非叶子节点分支数至少为3
- 结点内各关键字互不相等且按从小到大排列,下层节点内的关键字取值总是落在由上层节点关键字所划分的区间内
对于一个m阶B树,关键字个数范围 ceil(m/2) – 1 ~ m -1 (5阶 则是 2 ~ 4)
查找
多路查找
1. 从根节点开始,若key=k[i],查找成功,否则根据值范围去对应子树查找
插入
以关键字序列{1,2,6,7,11,4,8,13,10,5}为例,构建5阶B树,则一个结点最多关键字4个
- 1,2,6,7组成根节点
- 插入11,超出4个关键字,以中心关键字6进行拆分
- 插入4,8,13后
- 再插入10,以关键字10进行拆分
- 最后插入5
删除
需要先找到待删除的关键字,删除中若结点关键字不满足要求,则重新分配关键字
这是可能需要向其兄弟结点借关键字或者和其孩子结点进行关键字的交换,也可能需要进行结点的合并,其中,和当前结点的孩子进行关键字交换的操作可以保证删除操作总是发生在终端结点上
B+Tree
B+树是B树的升级版本,就目前情况,绝大部分都已经用B+树代替了B树了,文件管理、索引等等,当然,具体为什么可以看下面的优点介绍
- 区别:B+树非叶子节点不存储数据,每个叶子节点指向相邻的叶子节点
- 查找插入删除与B树类似
但 B+树提供了旋转功能,来尽可能的减少页的拆分
旋转发生在leaf Page已经满了、但是其左右兄弟节点没有满的情况下
B树与B+树的比较
- B树中关键字集合分布在整棵树中,叶节点中不包含任何关键字信息,而B+树关键字集合分布在叶子结点中,非叶节点只是叶子结点中关键字的索引;
- B树中任何一个关键字只出现在一个结点中,而B+树中的关键字必须出现在叶节点中,也可能在非叶结点中重复出现;
实例
MySQL索引 – 见另外博客
红黑树
- 本质:自平衡二叉树
在二叉查找树基础上,添加以下性质
1. 节点是红色或黑色
2. 根节点是黑色
3. 每个为空的叶子节点是黑色的
4. 每个红色节点的两个子节点都是黑色
5. 从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点
时间复杂度为O(lgn)
左旋
右旋
插入
1. 将红黑树当做一颗二叉查找树插入
2. 将插入的节点着色为“红色”(基于性质5)
3. 通过一系列旋转或重新着色等操作,使之重新成为一颗红黑树(不会违背性质1,2,3只需考虑性质4)
4. 进行情况判断,修正红黑树
删除
同理
优点
1. 红黑树的插入删除效率更高,任何不平衡都会在三次旋转内解决
2. 二叉平衡树比红黑树更为平衡,因此插入或删除时变动频次更高,但查找效率也更高
如何判断一个树是否有环
- 遍历结点,标识是否遍历过
- 记录每个结点到终点的坐标