数据结构-查找-二叉排序查找(平衡二叉树,B树,B+树概念)
0、为什么需要二叉排序树
1)数组存储方式:
优点:通过下标访问元素,速度快,对于有序数组,可以通过二分查找提高检索效率;
缺点:如果检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低;
2)链式存储结构:
优点:元素的插入删除效率高;
缺点:检索某个元素时,需要从头节点开始遍历。
3)树存储方式
可以整体提高数据存储和读取效率,例如二叉排序树,数据的检索速度,数据的插入,删除,修改速度都可以提高。(融合了数组和链表的优点)
前面的查找我们都是静态查找,因为数据集是有序存放,查找的方法有多种,可以使用折半,插值,斐波那契等,但是因为有序,在插入和删除操作上的效率并不高。
这时我们就需要一种动态查找方法,既可以高效实现查找,又可以使得插入和删除效率不错,这时我们可以考虑二叉排序树
1、二叉排序树(Binary Sort Tree)
又称二叉查找树,它或者是一棵空树,或者是具有以下性质的二叉树:
1)若它的左子树不为空,则左子树上所有结点的值均小于它的根结构的值;
2)若它的右子树不为空,则右子树上所有结点的值均大于它的根结构的值;
3)它的左,右子树也分别为二叉排序树(递归)。
虽然我们得到的数据是无序的,但是我们按照二叉排序树的方式组织,中序遍历二叉树则是有序的。当然构造二叉排序树的目的更多的是未来方便查找,插入和删除关键字的速度。
1)查找:
/* BiTree T 我们要搜索的二叉树 ElemType key我们要搜索的关键字 BiTree F 记录下我们的当前搜索子树的双亲结点 BiTree* P 当我们插入之前,会先搜索是否存在数据,若存在,不插入,若不存在,我们通过这个可以获取我们要插入的位置,直接插入即可 */ Status SearchBST(BiTree T, ElemType key, BiTree F, BiTree* P) { if (!T) { *P = F; //若是未找到则返回父节点位置 return FALSE; } else { if (T->data == key) { *P = T; //若是找到则P返回该结点位置 return TRUE; } else if (T->data < key) return SearchBST(T->rchild, key, T, P); else return SearchBST(T->lchild, key, T, P); } }
2)插入
Status InsertBST(BiTree* T, int key) { BiTree P,s; if (!T) return ERROR; if (!SearchBST(*T, key, NULL, &P)) { //没有查找到有重复数据,获取到了应该插入的位置 s = (BiTree)malloc(sizeof(BiTNode)); s->data = key; s->lchild = s->rchild = NULL; if (!P) //空树 *T = s; else if (key < P->data) //插入左子树 P->lchild = s; else P->rchild = s; return OK; } else return ERROR; }
3)删除
如果是叶子结点,则直接删除;
如果结点只存在左子树或右子树,则直接补上;
如果同时存在左子树和右子树,则按照中序遍历的顺序将删除结点的前驱或者后继补上即可;具体步骤:
1)寻找删除结点的右孩子的左孩子的左孩子。。。(就是右子树的最左结点q),注意,这个最左结点可能存在右孩子,所以同时需要记录q的双亲结点f。
2)将q结点的内容赋值到要删除的结点;
3)对结点q进行操作,分两种情况:
①如果f就是要删除的结点,说明f只存在一个右斜树孩子,因此f->rchild = q->rchild;
②如果f不是要删除的结点,q可能存在右孩子,此时直接将q的右孩子赋予f的左孩子f->lchild = q->rchild;
Status Delete(BiTree* T) { BiTree q,f; if (!*T) return ERROR; if (!(*T)->lchild) //若是左子树不存在,我们只需要接到右子树 { q = *T; *T = (*T)->rchild; free(q); } else if (!(*T)->rchild) //若右子树不存在,接入左子树 { q = *T; *T = (*T)->lchild; free(q); } else //两边都存在,我们可以选择将右子树最小,或者左子树最大接入,这里选择右子树最小 { f = *T; //f指向q的双亲结点 q = (*T)->rchild; while (q->lchild) { f = q; // f为为需要补上结点的双亲结点 q = q->lchild; //找到右子树最小,注意其可能存在右子树,我们要进行保存,接入其父节点 } //将最小的数据更新到根节点处即可,然后记录最小点处,删除即可 (*T)->data = q->data; if (f != (*T)) f->lchild = q->rchild; else f->rchild = q->rchild; //当右子树是一个右斜树 free(q); } return TRUE; } Status DeleteBST(BiTree* T, int key) { if (!*T) return ERROR; else { if ((*T)->data == key) //找到了,开始删除 { //删除该结点,由于要分情况讨论,所以另外写一个函数 } else if ((*T)->data < key) DeleteBST(&(*T)->rchild, key); else DeleteBST(&(*T)->lchild, key); } }
Java版本:🔗
package com.ys.tree; public class BinaryTree implements Tree { //表示根节点 private Node root; //查找节点 public Node find(int key) { Node current = root; while(current != null){ if(current.data > key){//当前值比查找值大,搜索左子树 current = current.leftChild; }else if(current.data < key){//当前值比查找值小,搜索右子树 current = current.rightChild; }else{ return current; } } return null;//遍历完整个树没找到,返回null } //插入节点 public boolean insert(int data) { Node newNode = new Node(data); if(root == null){//当前树为空树,没有任何节点 root = newNode; return true; }else{ Node current = root; Node parentNode = null; while(current != null){ parentNode = current; if(current.data > data){//当前值比插入值大,搜索左子节点 current = current.leftChild; if(current == null){//左子节点为空,直接将新值插入到该节点 parentNode.leftChild = newNode; return true; } }else{ current = current.rightChild; if(current == null){//右子节点为空,直接将新值插入到该节点 parentNode.rightChild = newNode; return true; } } } } return false; } //中序遍历 public void infixOrder(Node current){ if(current != null){ infixOrder(current.leftChild); System.out.print(current.data+" "); infixOrder(current.rightChild); } } //前序遍历 public void preOrder(Node current){ if(current != null){ System.out.print(current.data+" "); infixOrder(current.leftChild); infixOrder(current.rightChild); } } //后序遍历 public void postOrder(Node current){ if(current != null){ infixOrder(current.leftChild); infixOrder(current.rightChild); System.out.print(current.data+" "); } } //找到最大值 public Node findMax(){ Node current = root; Node maxNode = current; while(current != null){ maxNode = current; current = current.rightChild; } return maxNode; } //找到最小值 public Node findMin(){ Node current = root; Node minNode = current; while(current != null){ minNode = current; current = current.leftChild; } return minNode; } @Override public boolean delete(int key) { Node current = root; Node parent = root; boolean isLeftChild = false; //查找删除值,找不到直接返回false while(current.data != key){ parent = current; if(current.data > key){ isLeftChild = true; current = current.leftChild; }else{ isLeftChild = false; current = current.rightChild; } if(current == null){ return false; } } //如果当前节点没有子节点 if(current.leftChild == null && current.rightChild == null){ if(current == root){ root = null; }else if(isLeftChild){ parent.leftChild = null; }else{ parent.rightChild = null; } return true; //当前节点有一个子节点,右子节点 }else if(current.leftChild == null && current.rightChild != null){ if(current == root){ root = current.rightChild; }else if(isLeftChild){ parent.leftChild = current.rightChild; }else{ parent.rightChild = current.rightChild; } return true; //当前节点有一个子节点,左子节点 }else if(current.leftChild != null && current.rightChild == null){ if(current == root){ root = current.leftChild; }else if(isLeftChild){ parent.leftChild = current.leftChild; }else{ parent.rightChild = current.leftChild; } return true; }else{ //当前节点存在两个子节点 Node successor = getSuccessor(current); if(current == root){ root= successor; }else if(isLeftChild){ parent.leftChild = successor; }else{ parent.rightChild = successor; } successor.leftChild = current.leftChild; } return false; } public Node getSuccessor(Node delNode){ Node successorParent = delNode; Node successor = delNode; Node current = delNode.rightChild; while(current != null){ successorParent = successor; successor = current; current = current.leftChild; } //后继节点不是删除节点的右子节点,将后继节点替换删除节点 if(successor != delNode.rightChild){ successorParent.leftChild = successor.rightChild; successor.rightChild = delNode.rightChild; } return successor; } }
平衡二叉树:是一种二叉排序树,且空树或任一结点左右子树高度差的绝对值不超过1,即|BF|<=1(目的是使二叉排序树的左右均匀)
平衡因子:将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF(Balance Factor) BF=hl-hr
B树(B-树)是一种平衡的多路查找树。2-3树和2-3-4树都是B树的特例。节点最大的孩子数组称为B树的阶(order),因此,2-3树是3阶B树,2-3-4树是4阶B树。
B+树是应文件系统所需而出的一种B树的变形树,在B树中,每一个元素树中只出现一次,而B+树中,出现在分支节点中的元素会被当做他们在该分支节点位置的中序后继者(叶子节点)中再次列出。另外,每一个叶子节点都会保存一个指向后一叶子节点的指针。
红黑树:能够以较快的时间O(logN)来搜索一棵树,我们需要保证树总是平衡的(或者大部分是平衡的),也就是说每个节点的左子树节点个数和右子树节点个数尽量相等。红-黑树的就是这样的一棵平衡树,对一个要插入的数据项(删除也是),插入例程要检查会不会破坏树的特征,如果破坏了,程序就会进行纠正,根据需要改变树的结构,从而保持树的平衡。
有如下两个特征:
①、节点都有颜色;
②、在插入和删除的过程中,要遵循保持这些颜色的不同排列规则。
第一个很好理解,在红-黑树中,每个节点的颜色或者是黑色或者是红色的。当然也可以是任意别的两种颜色,这里的颜色用于标记,我们可以在节点类Node中增加一个boolean型变量isRed,以此来表示颜色的信息。
第二点,在插入或者删除一个节点时,必须要遵守的规则称为红-黑规则:
1.每个节点不是红色就是黑色的;
2.根节点总是黑色的;
3.如果节点是红色的,则它的子节点必须是黑色的(反之不一定),(也就是从每个叶子到根的所有路径上不能有两个连续的红色节点);
4.从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。
从根节点到叶节点的路径上的黑色节点的数目称为黑色高度,规则 4 另一种表示就是从根到叶节点路径上的黑色高度必须相同。
注意:新插入的节点颜色总是红色的,这是因为插入一个红色节点比插入一个黑色节点违背红-黑规则的可能性更小,原因是插入黑色节点总会改变黑色高度(违背规则4),但是插入红色节点只有一半的机会会违背规则3(因为父节点是黑色的没事,父节点是红色的就违背规则3)。另外违背规则3比违背规则4要更容易修正。当插入一个新的节点时,可能会破坏这种平衡性,