3树
树
1树定义
- 树是n(n>=0)个节点的有限集。当n=0时,称为空树。在任意一个非空树中有如下特点:
- 1有且仅有一个特定的称为根的节点
- 2当n>1时,其余节点可分为m(m>0)个互不相交的有限集,每一个集合本身又是一个数,并称为根的子树
- 概念:根节点,叶子结点,父节点,孩子节点,兄弟节点
2二叉树
- 二叉树是树的一种特殊形式,这种树的每个节点最多有2个孩子节点(最多有2个,可能只有1个,或者没有孩子节点)
- 二叉树节点的两个孩子节点,一个称为左孩子,一个称为右孩子
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x){
val=x;
}
}
3满二叉树
- 一个二叉树额所有非叶子节点都存在左右孩子,并且所有叶子节点都在同一层级上,那么这个树就是满二叉树
4完全二叉树
- 对一个有n个节点的二叉树,按层级顺序编号,则所有节点的编号为从1到n。如果这个树所有节点和同样深度的满二叉树的编号为从1到n的节点位置相同,则这个二叉树为完全二叉树
5二叉树存储
- 链式存储(最直观)。每一个节点包含3部分:1存储数据的data变量;2指向左孩子的left指针;3指向右孩子的right指针
- 数组存储。按照层级顺序把二叉树的节点放到数组中对应的位置上,如果某一个节点的左孩子或右孩子空缺,则数组的相应位置也会空出来。
6二叉树应用之查找(二叉查找树)和维持相对顺序(二叉平衡树)
-
二叉查找树:如果左子树不为空,则左子树上所有节点的值均小于根节点的值;如果右子树不为空,则右子树上所有节点的值均大于节点的值;左右子树也都是二叉查找树
-
对于一个节点分布相对均衡的二叉查找树来说,如果节点总数是n,那么搜索节点的时间复杂度是O(logn),和树的深度是一样的
-
二叉平衡树:用于树的自平衡,主要用红黑树,AVL树,树堆等,还有二叉堆,之后会写
7二叉树的遍历
- 前序遍历:根,左,右
- 中序遍历:左,根,右
- 后序遍历:左,右,根
- 层次遍历
7.1递归实现前序,中序,后序遍历
//构建二叉树
public static TreeNode createBinaryTree(LinkedList<Integer> inputList){
TreeNode node=null;
if (inputList==null||inputList.isEmpty()){
return null;
}
Integer data=inputList.removeFirst();
if (data!=null){
node=new TreeNode(data);
node.left=createBinaryTree(inputList);
node.right=createBinaryTree(inputList);
}
return node;
}
//二叉树前序遍历
public static void preOrderTraveral(TreeNode node){
if (node==null){
return;
}
System.out.println(node.val);
preOrderTraveral(node.left);
preOrderTraveral(node.right);
}
//二叉树中序遍历
public static void inOrderTraveral(TreeNode node){
if (node==null){
return;
}
inOrderTraveral(node.left);
System.out.println(node.val);
inOrderTraveral(node.right);
}
//二叉树后序遍历
public static void postOrderTraveral(TreeNode node){
if (node==null){
return;
}
postOrderTraveral(node.left);
postOrderTraveral(node.right);
System.out.println(node.val);
}
public static void main(String[] args) {
LinkedList<Integer> inputList = new LinkedList<>(Arrays.asList(new Integer[]{3, 2, 9, null, null, 10, null, null, 8, null, 4}));
TreeNode treeNode=createBinaryTree(inputList);
System.out.println("前序遍历: ");
preOrderTraveral(treeNode);
System.out.println("中序遍历: ");
inOrderTraveral(treeNode);
System.out.println("后序遍历: ");
postOrderTraveral(treeNode);
}
7.2非递归实现前序,中序,后序
/**
* 前序遍历
* 非递归
*/
public void preOrder1(BinaryNode<AnyType> Node)
{
Stack<BinaryNode> stack = new Stack<>();
while(Node != null || !stack.empty())
{
while(Node != null)
{
System.out.print(Node.element + " ");
stack.push(Node);
Node = Node.left;
}
if(!stack.empty())
{
Node = stack.pop();
Node = Node.right;
}
}
}
/**
* 中序遍历
* 非递归
*/
public void midOrder1(BinaryNode<AnyType> Node)
{
Stack<BinaryNode> stack = new Stack<>();
while(Node != null || !stack.empty())
{
while (Node != null)
{
stack.push(Node);
Node = Node.left;
}
if(!stack.empty())
{
Node = stack.pop();
System.out.print(Node.element + " ");
Node = Node.right;
}
}
}
/**
* 后序遍历
* 非递归
*/
public void posOrder1(BinaryNode<AnyType> Node)
{
Stack<BinaryNode> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
int i = 1;
while(Node != null || !stack1.empty())
{
while (Node != null)
{
stack1.push(Node);
stack2.push(0);
Node = Node.left;
}
while(!stack1.empty() && stack2.peek() == i)
{
stack2.pop();
System.out.print(stack1.pop().element + " ");
}
if(!stack1.empty())
{
stack2.pop();
stack2.push(1);
Node = stack1.peek();
Node = Node.right;
}
}
}
7.3层序遍历
public static void levelOrderTraversal(TreeNode root){
//LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用
Queue<TreeNode> queue=new LinkedList<TreeNode>();
queue.offer(root);
while (!queue.isEmpty()){
TreeNode node=queue.poll();
System.out.println(node.val);
if (node.left!=null){
queue.offer(node.left);
}
if (node.right!=null){
queue.offer(node.right);
}
}
}
8二叉堆
8.1二叉堆概念
- 二叉堆本质上是一种完全二叉树,它分为两个类型
- 最大堆:任何一个父节点的值都大于或等于它左,右孩子节点的值
- 最小堆:任何一个父节点的值都小于或等于它左,右孩子节点的值
8.2二叉堆的自我调整
-
插入节点:插在最后一个位置,不断和父节点比较进行上浮
-
删除节点:删除的是处于堆顶的节点,将最后一个节点临时补到原来堆顶的位置,接下来不断比较子节点然后下沉就行了
-
构建二叉堆:就是把一个无序的完全二叉树调整为二叉堆,本质就是让所有非叶子节点依次下沉,从最后一个非叶子节点开始
-
对于插入节点和删除节点时间复杂度都是O(logn),对于构建二叉堆的时间复杂度就是O(n)
8.3二叉堆的代码实现(以最小堆为例)
- 需要明确:二叉堆虽然是一个完全二叉树,但它的存储方式并不是链式存储,而是顺序存储,就是二叉堆的所有节点都存储在数组中
- 假设父节点的下标是parent,那么它的左孩子下标就是2 * parent+1;右孩子下标就是2 * parent+2
//上浮调整
public static void upAdjust(int[] array){
int childIndex=array.length-1;
int parentIndex=(childIndex-1)/2;
//temp保存插入的叶子节点值,用于最后的赋值
int temp=array[childIndex];
while (childIndex>0 && temp<array[parentIndex]){
//无须真正交换,单向赋值就行
array[childIndex]=array[parentIndex];
childIndex=parentIndex;
parentIndex=(parentIndex-1)/2;
}
array[childIndex]=temp;
}
//下沉调整
//array:要调整的堆 parentIndex:要下沉的父节点 length:堆的有效大小
public static void downAdjust(int[] array,int parentIndex,int length){
//temp保存父节点的值,用于最后的赋值
int temp=array[parentIndex];
int childIndex=2*parentIndex+1;
while (childIndex<length){
//如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
if (childIndex+1<length && array[childIndex+1]<array[childIndex]){
childIndex++;
}
//如果父节点小于任何一个孩子的值,则直接跳出
if (temp<=array[childIndex]){
break;
}
array[parentIndex]=array[childIndex];
parentIndex=childIndex;
childIndex=2*childIndex+1;
}
array[parentIndex]=temp;
}
//构建堆
public static void buildHeap(int[] array){
//从最后一个非叶子节点开始,以此做下沉调整
for (int i=(array.length-2)/2;i>=0;i--){
downAdjust(array,i,array.length);
}
}
public static void main(String[] args) {
int[] array = {1, 3, 2, 6, 5, 7, 8, 9, 10, 0};
upAdjust(array);
System.out.println(Arrays.toString(array));
}
9优先队列
9.1概念
- 最大优先队列:无论入队顺序如何,都是当前最大的元素优先出队
- 最小优先队列:无论入队顺序如何,都是当前最小的元素优先出队
10二叉查找树
10.1概念
- 如果左子树不为空,则左子树上所有节点的值均小于根节点的值
- 如果右子树不为空,则右子树上所有节点的值均大于根节点的值
- 左右子树也都是二叉查找树
- 对于一个节点分布相对平衡的二叉查找树,如果节点总数是n,那么查找节点的时间复杂度就是O(logn),和树的深度成正比
- 对于二叉查找树进行中序遍历就能输出完全升序的排列
- 正是因为上面这个原因,二叉查找树也被称为二叉排序树
10.2二叉查找树的插入和删除
- 插入就直接插入
- 删除要分为三种情况
- 情况1:待删除的节点没有叶子节点:直接删除
- 情况2:待删除的节点有一个孩子:让那个孩子节点取代被删除的节点
- 情况3:待删除的节点有两个孩子:选择与带删除节点最接近的节点来取代它
private Node root;
//中序遍历
public static void inOrderTraversal(Node node){
if(node == null){
return;
}
inOrderTraversal(node.left);
System.out.print(node.data + " ");
inOrderTraversal(node.right);
}
//查找结点
public Node search(int data) {
Node targetNode = root;
while (targetNode!=null && targetNode.data != data) {
if (data > targetNode.data) {
targetNode = targetNode.right;
} else {
targetNode = targetNode.left;
}
}
if(targetNode == null){
System.out.println("未找到结点:" + data);
} else {
System.out.println("已找到结点:" + data);
}
return targetNode;
}
//插入结点
public boolean insert(int data) {
Node node = new Node(data);
if(root == null){
root = node;
return true;
}
Node targetNode = root;
while (targetNode != null) {
if( data == targetNode.data){
System.out.println("二叉查找树中已有重复的结点:" + data);
return false;
}
else if (data > targetNode.data) {
if(targetNode.right == null){
targetNode.right = node;
return true;
}
targetNode = targetNode.right;
}
else {
if(targetNode.left == null){
targetNode.left = node;
return true;
}
targetNode = targetNode.left;
}
}
return true;
}
//删除结点
public boolean delete(int data) {
Node targetNode = root;
Node parentNode = new Node(data);
//判断待删除结点是否存在
while (targetNode.data != data) {
parentNode = targetNode;
if (data > targetNode.data) {
targetNode = targetNode.right;
} else {
targetNode = targetNode.left;
}
if (targetNode == null) {
// 没有找到待删除结点
return false;
}
}
// 待删除结点没有子节点
if (targetNode.right==null && targetNode.left==null) {
if (targetNode == root) {
//待删除结点是根结点
root = null;
} else {
if (parentNode.right == targetNode) {
parentNode.right = null;
} else {
parentNode.left = null;
}
}
}
//待删除结点有一个子结点(右)
else if(targetNode.left == null) {
if(targetNode == root) {
root = targetNode.right;
} else if(parentNode.right == targetNode) {
parentNode.right = targetNode.right;
} else {
parentNode.left = targetNode.right;
}
}
//待删除结点有一个子结点(左)
else if(targetNode.right == null) {
if(targetNode == root) {
root = targetNode.left;
} else if(parentNode.right == targetNode) {
parentNode.right = targetNode.left;
} else {
parentNode.left = targetNode.left;
}
}
//待删除结点有两个子结点
else {
//待删除结点的后继结点的父结点
Node successParentNode = targetNode;
//待删除结点的后继结点
Node successNode = targetNode.right;
while(successNode.left != null)
{
successParentNode = successNode;
successNode = successNode.left;
}
//把后继结点复制到待删除结点位置
targetNode.data = successNode.data;
//删除后继结点
if(successParentNode.right == successNode) {
successParentNode.right = successNode.right;
} else {
successParentNode.left = successNode.right;
}
}
return true;
}
// 结点类
private class Node {
int data;
Node right;
Node left;
Node(int data){
this.data = data;
}
}
11平衡二叉树
11.1概念
- 平衡二叉树也被称为AVL树,它在每次插入,删除节点之后,可以进行“自平衡”,也就是通过一系列调整查询达到平衡状态
- 对于AVL树的每一个节点,平衡因子是它的左子树和右子树高度的差值,只有当二叉树所有节点的平衡因子都是-1,0,1这三个值的时候,这课二叉树才是一棵合格的AVL树
- AVL树对平衡因子的限制,保证了任意节点的两棵子树的高度差都不超过1,这种状态被称为高度平衡
11.2AVL树旋转
- 左左局面:右旋操作
- 右右局面:左旋操作
- 左右局面:先左旋,再右旋
- 左右局面:先右旋,再左旋
public class AVLTree {
private TreeNode root;
/*
* 获取树的高度
*/
private int height(TreeNode node) {
if (node != null)
return node.height;
return 0;
}
public int height() {
return height(root);
}
//查找结点
public TreeNode search(TreeNode node, int data) {
while (node!=null) {
if (data < node.data)
node = node.left;
else if (data > node.data)
node = node.right;
else
return node;
}
return node;
}
//左左局面旋转
private TreeNode leftLeftRotation(TreeNode node) {
//leftChildNode 对应示意图中的结点B
TreeNode leftChildNode = node.left;
node.left = leftChildNode.right;
leftChildNode.right = node;
//刷新结点A和结点B的高度
node.height = Math.max(height(node.left), height(node.right)) + 1;
leftChildNode.height = Math.max(height(leftChildNode.left), node.height) + 1;
//返回旋转后的父结点
return leftChildNode;
}
//右右局面旋转
private TreeNode rightRightRotation(TreeNode node) {
//rightChildNode 对应示意图中的结点B
TreeNode rightChildNode = node.right;
node.right = rightChildNode.left;
rightChildNode.left = node;
//刷新结点A和结点B的高度
node.height = Math.max(height(node.left), height(node.right)) + 1;
rightChildNode.height = Math.max(height(rightChildNode.right), node.height) + 1;
//返回旋转后的父结点
return rightChildNode;
}
//左右局面旋转
private TreeNode leftRightRotation(TreeNode node) {
//先做左旋
node.left = rightRightRotation(node.left);
//再做右旋
return leftLeftRotation(node);
}
//右左局面旋转
private TreeNode rightLeftRotation(TreeNode node) {
//先做右旋
node.right = leftLeftRotation(node.right);
//再做左旋
return rightRightRotation(node);
}
//插入结点
public void insert(int data) {
root = insert(root, data);
}
//插入结点详细过程(递归)
private TreeNode insert(TreeNode node, int data) {
if (node == null) {
node = new TreeNode(data);
} else {
if (data < node.data) {
//新结点小于当前结点,选择当前结点的左子树插入
node.left = insert(node.left, data);
// 插入节点后,若AVL树失去平衡,则进行相应的调节。
if (node.getBalance() == 2) {
if (data < node.left.data) {
node = leftLeftRotation(node);
} else {
node = leftRightRotation(node);
}
}
} else if (data > node.data) {
//新结点大于当前结点,选择当前结点的右子树插入
node.right = insert(node.right, data);
// 插入节点后,若AVL树失去平衡,则进行相应的调节。
if (node.getBalance() == -2) {
if (data > node.right.data) {
node = rightRightRotation(node);
} else {
node = rightLeftRotation(node);
}
}
} else {
System.out.println("AVL树中已有重复的结点!");
}
}
//刷新结点的高度
node.height = Math.max(height(node.left), height(node.right)) + 1;
return node;
}
//删除结点
public void remove(int data) {
TreeNode deletedNode;
if ((deletedNode = search(root, data)) != null)
root = remove(root, deletedNode);
}
//删除结点详细过程(递归)
private TreeNode remove(TreeNode node, TreeNode deletedNode) {
// 根为空 或者 没有要删除的节点,直接返回null。
if (node==null || deletedNode==null)
return null;
if (deletedNode.data < node.data){
//待删除结点小于当前结点,在当前结点的左子树继续执行
node.left = remove(node.left, deletedNode);
// 删除节点后,若AVL树失去平衡,则进行相应的调节。
if (height(node.right) - height(node.left) == 2) {
TreeNode r = node.right;
if (height(r.left) > height(r.right))
node = rightLeftRotation(node);
else
node = rightRightRotation(node);
}
} else if (deletedNode.data > node.data) {
//待删除结点大于当前结点,在当前结点的右子树继续执行
node.right = remove(node.right, deletedNode);
// 删除节点后,若AVL树失去平衡,则进行相应的调节。
if (height(node.left) - height(node.right) == 2) {
TreeNode l = node.left;
if (height(l.right) > height(l.left))
node = leftRightRotation(node);
else
node = leftLeftRotation(node);
}
} else {
// tree的左右孩子都非空
if ((node.left!=null) && (node.right!=null)) {
if (height(node.left) > height(node.right)) {
// 如果node的左子树比右子树高,找出左子树最大结点赋值给Node,并删除最小结点
TreeNode max = maximum(node.left);
node.data = max.data;
node.left = remove(node.left, max);
} else {
// 如果node的右子树比左子树高,找出右子树最小结点赋值给Node,并删除最小结点
TreeNode min = minimum(node.right);
node.data = min.data;
node.right = remove(node.right, min);
}
} else {
node = (node.left!=null) ? node.left : node.right;
}
}
node.height = Math.max(height(node.left), height(node.right)) + 1;
return node;
}
//找出结点node为根的子树的最大节点
private TreeNode maximum(TreeNode node) {
if (node == null)
return null;
while(node.right != null)
node = node.right;
return node;
}
//找出结点node为根的子树的最小节点
private TreeNode minimum(TreeNode node) {
if (node == null)
return null;
while(node.left != null)
node = node.left;
return node;
}
//中序遍历
public static void inOrderTraversal(TreeNode node) {
if(node != null) {
inOrderTraversal(node.left);
System.out.print(node.data+" ");
inOrderTraversal(node.right);
}
}
//层序遍历
public static void levelOrderTraversal(TreeNode root){
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode node = queue.poll();
System.out.print(node.data+" ");
if(node.left != null){
queue.offer(node.left);
}
if(node.right != null){
queue.offer(node.right);
}
}
}
class TreeNode {
int data;
int height;
TreeNode left;
TreeNode right;
public TreeNode(int data) {
this.data = data;
this.height = 0;
}
//获得结点的平衡因子
public int getBalance(){
int left = (this.left==null ? 0:this.left.height);
int right = (this.right==null ? 0:this.right.height);
return left - right;
}
}
public static void main(String[] args) {
AVLTree tree = new AVLTree();
int input[]= {5,3,7,2,4,6,9,1};
for(int i=0; i<input.length; i++) {
tree.insert(input[i]);
}
System.out.println("中序遍历: ");
inOrderTraversal(tree.root);
System.out.println();
System.out.println("层序遍历: ");
levelOrderTraversal(tree.root);
System.out.println();
System.out.printf("高度: %d\n", tree.height());
int deletedData = 3;
System.out.printf("删除根节点: %d\n", deletedData);
tree.remove(deletedData);
System.out.println("中序遍历: ");
inOrderTraversal(tree.root);
System.out.println();
System.out.println("层序遍历: ");
levelOrderTraversal(tree.root);
System.out.println();
System.out.printf("高度: %d\n", tree.height());
}
}
12红黑树
12.1概念
- 红黑树规则如下:
- 节点是红色或者黑色
- 根节点是黑色
- 每个叶子节点都是黑色的空节点(null节点)
- 每个红色节点的子节点都是黑色的(即不存在两个连续的红色节点)
- 从任意节点到其每个叶子的所有路径都包含相同数目的黑色节点
- 正因为这些规则的限制,才保证了红黑树的平衡性,红黑树从根到叶子的最长路径不会超过最短路径的2倍
- 当向红黑树插入或删除元素时,会破坏红黑树的规则,其实就需要变色和旋转(旋转和AVL树一样)
12.2红黑树的插入
- 局面一:新节点A位于树根,没有父节点:这种局面直接让新节点变色为黑色
- 局面二:新节点B的父节点是黑色:这种局面不需要修改
- 局面三:新节点D的父节点和叔叔节点都是红色的:先让父节点变为黑色,再让叔叔节点变为黑色,最后让叔叔和父节点的共同父节点(祖父节点)变为红色
- 局面四:新节点D的父节点是红色,叔叔节点是黑色或没有叔叔,且新节点是父节点的右孩子,父节点是祖父节点的左孩子:以父节点为轴做一次旋转,使得新节点D称为父节点,原来的父节点成为D的左孩子,这样一来进入了局面五
- 局面五:新节点D的父节点是红色,叔叔节点是黑色或者没有叔叔,且新节点是父节点的左孩子,父节点是祖父节点的左孩子:首先以祖父节点A为轴做一次右旋转,使得父节点B成为祖父节点,祖父节点A称为节点B的右孩子,接着让节点B变为黑色,让节点A变为红色
- 如果局面四和局面五当中的父节点B是祖父节点A的右孩子,我们只需要把原来左旋操作变成右旋操作就行了
12.3红黑树的删除
- 第1步:如果待删除节点有两个非空的孩子节点,转化成待删除节点只有一个孩子或没有孩子的情况(和二叉查找树一样,找一个最接近这个节点的子节点)
- 第2步:根据带删除节点和其唯一子节点的颜色,分不同局面来处理
-
- 局面1:自身是红色,子节点是黑色:直接删除自身就行
- 局面2:自身是黑色,子节点是红色:删除自身,将子节点变成黑色(因为路径凭空少了一个黑色节点)
- 局面3:自身是黑色,子节点也是黑色:先删除自身节点,然后进入6种子局面(假设自身叫节点1,子节点叫节点2)
-
- 子局面1:节点2是根节点:不需要调整
- 子局面2:节点2的父亲,兄弟,侄子都是黑色的:我们直接把节点2的兄弟节点B改为红色
- 子局面3:节点2的兄弟节点是红色:先以节点2的父节点A为轴进行左旋,然后将节点A变成红色,兄弟节点B变成黑色
- 子局面4:节点2的父节点是红色,兄弟和侄子节点是黑色:直接让父节点A变成黑色,兄弟节点B变成红色
- 子局面5:节点2的父节点随意,兄弟节点B是黑色右孩子,左侄子节点C是黑色,右侄子节点D是黑色:先以兄弟节点B为轴进行右转,然后将节点B变为黑色,节点C变为黑色
- 子局面6:节点2的父节点随意,兄弟节点B是黑色右孩子,右侄子节点D是红色:首先以父节点A为轴左旋,在让节点A和节点B颜色交换,并且节点D变为黑色
12.3使用场景
- AVL树是高度平衡二叉查找树,要求每个节点的左右子树高度差不超过1;而红黑树则要宽松一点,要求任何一条路径的长度不超过其他路径长度的2倍
- 正因为这个差别,AVL树的查找效率更高,但维持平衡的成本也更高。在需要频繁查找时,选用AVL树更合适,在需要频繁插入,删除时,选用红黑树更合适
13B树
13.1概念
- 数据库的索引并没有采用二叉树,虽然平衡二叉树查找操作上的比较次数确实是最少的,但是在现实应用中,我们不得不考虑磁盘和内存读写速度的差距。我们只以索引树的节点为基本单元,每次把单一节点从磁盘读取到内存中,进行后续操作
- 由于磁盘I/O次数等于索引树的高度,所以为了减少磁盘I/O。我们需要把原来瘦高的树结构变成矮胖的,让每一个节点承载更多的元素,拥有更多的孩子,因此有了B树和B+树
13.2定义
- B树单一节点拥有的最多子节点数量,称为B树的阶,一个m阶的B树,具有如下几个特征:
- 根节点至少有两个子节点
- 每个中间节点都包含k-1个元素(也称为关键字)和k个孩子,其中m/2<=k<=m
- 每一个叶子节点都包含k-1个元素,其中m/2<=k<=m
- 所有叶子节点都位于同一层
- 每个节点中的元素从小到大排序,节点当中k-1个元素正好是k个孩子包含的元素的值域划分
13.3插入
- B树的插入操作是在叶子节点进行的, 可以分为两种情况:
- 情况1:插入元素未改变规则,即叶子节点元素数量不超过m-1:不需要任何改变
- 情况2:插入元素使叶子节点元素过多,超过m-1个元素:需要进行节点分裂,把节点m/2位置的元素上升到父节点,剩余的左半边元素和右半边元素分成两个独立的叶子节点
13.4删除
- 情况1:删除元素在叶子节点,未改变规则:不用做任何调整
- 情况2:删除元素在叶子节点,剩余元素不足,相邻兄弟节点有多余元素:向兄弟节点借一个元素
- 情况3:删除元素在叶子节点,剩余元素不足,相邻兄弟节点没有多余远古三:可以将父节点中的元素和兄弟节点元素进行合并
- 情况4:删除元素在中间节点:选择该元素的前驱或后驱元素来顶替它的位置
13.5弊端
- 数据库的查询不止涉及到单一结果查询,也会涉及到一个区间内结果的查询(比如查询成绩在60-80分的所有学生)
- 对于前者B树很容易实现,但对于后者B树实现起来会非常困难,需要进行中序遍历,在父节点和子节点之间不断切换
- 因此需要B+树
14B+树
14.1概念
- 一个m阶B+数具有如下几个特征
- 有K个子树的中间节点包含k个元素(B树中是k-1个元素),每个元素不保存数据,所有数据都保存在叶子节点
- 所有的叶子节点包含了全部元素,依照元素的大小升降排列,叶子节点之间用双向指针相连接
- 所有中间节点的元素都同时存在于子节点,在子节点元素中是最大(或最小)元素

浙公网安备 33010602011771号