数据结构(一)二叉树 & avl树 & 红黑树 & B-树 & B+树 & B*树 & R树

参考文档:

avl树:http://lib.csdn.net/article/datastructure/9204

avl树:http://blog.csdn.net/javazejian/article/details/53892797

红黑树:http://daoluan.net/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E7%AE%97%E6%B3%95/2013/09/25/rbtree-is-not-difficult.html

trie树:https://www.cnblogs.com/huangxincheng/archive/2012/11/25/2788268.html

b,b+,b* 树 :https://www.cnblogs.com/wjoyxt/p/5501706.html

b,b+ 树 & linux磁盘存储 :https://www.cnblogs.com/vincently/p/4526560.html

b,b+树 & mysql索引 & 索引的结构优化:http://blog.codinglabs.org/articles/theory-of-mysql-index.html

 

树:

树(Tree)是n(n≥0)个结点的有限集T,并且当n>0时满足下列条件:
     (1)有且仅有一个特定的称为根(Root)的结点;
     (2)当n>1时,其余结点可以划分为m(m>0)个互不相交的有限集T1、T2 、…、Tm,每个集Ti(1≤i≤m)均为树,且称为树T的子树(SubTree)。
    特别地,不含任何结点(即n=0)的树,称为空树。
如下就是一棵树的结构:

两种树:

  • 有序树:若将树中每个结点的各子树看成是从左到右有次序的(即不能互换),则称该树为有序树
  • 无序树:若将树中每个结点的各子树看成是从左到右无次序的(即可以互换),则称该树为无序树

     

多叉树的广度优先遍历与深度优先遍历

举个栗子:

多叉树

 

广度优先遍历:a b c d e f g h i

深度优先遍历:a b d e i c f g h (相当于二叉树的先序遍历)

实现:

public class MultiwayTree {
    private static MultiwayNode root;

public MultiwayTree(){
    //        a
    //    b       c
    // d     e  f  g  h
    //     i
    MultiwayNode h=new MultiwayNode("h");
    MultiwayNode i=new MultiwayNode("i");
    MultiwayNode g=new MultiwayNode("g");
    MultiwayNode f=new MultiwayNode("f");
    MultiwayNode d = new MultiwayNode("d");
    MultiwayNode e = new MultiwayNode("e",i);
    MultiwayNode b = new MultiwayNode("b", d, e);
    MultiwayNode c = new MultiwayNode("c", f, g,h);
    root = new MultiwayNode("a", b, c);
}
//深度优先遍历
static void depthFirstSearch(MultiwayNode node){
    if(node==null){
        return;
    }
    List<MultiwayNode> children=node.children;
    if(children==null){
        return;
    } 
    System.out.print(node.value+" ");
    for(MultiwayNode no:children){
        depthFirstSearch(no);
    }

}
//广度优先遍历
static void breadthFirstSearch(){
    List<MultiwayNode> list=new ArrayList<MultiwayNode>();
    list.add(root);
    breadthFirstSearch(list);
}

//广度优先遍历
static void breadthFirstSearch(List<MultiwayNode> nodes){
    List<MultiwayNode> level=new ArrayList<MultiwayNode>();
    for(MultiwayNode node:nodes){
        System.out.print(node.value+" ");
        List<MultiwayNode> children=node.children;
        level.addAll(children);
        children.clear();
    }
    if(level.size()>0){
        breadthFirstSearch(level);
    }
}
    public static void main(String[] args) {
        MultiwayTree head=new MultiwayTree();
        depthFirstSearch(head.root);
    }

}
class MultiwayNode{
    public String value;
    public List<MultiwayNode> children=null;
    public MultiwayNode(String value,MultiwayNode... nodes){
        this.value=value;
        children=new ArrayList<MultiwayNode>();
        for(MultiwayNode node:nodes){
            children.add(node);
        }
    }
}
View Code

二叉树:

        二叉树是每个结点最多有两个孩子,且其子树有左右之分的有序树

二叉树的两个特殊形态

  • 满二叉树

   一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树

  • 完全二叉树(Complete Binary Tree):

   若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树

 

 

二叉树得到一般算法

  求二叉树的最大深度:

int getMaxDeath(Node node) {
        if (node == null) {
            return 0;
        }
        int left = getMaxDeath(node.leftchild);
        int right = getMaxDeath(node.leftchild);
        return Math.max(left, right) + 1;
    }
View Code

  求二叉树的最小深度:

int getMinDepth(Node root) {
        if (root == null) {
            return 0;
        }
        int left=getMinDepth(root.leftchild);
        int right=getMinDepth(root.rightchild);
        return Math.min(left, right) + 1;
    }
View Code

  求二叉树的总节点数

//求二叉树的节点总数
    int getTotalNumNode(Node root) {
        if (root == null) {
            return 0;
        }
        int left = getTotalNumNode(root.leftchild);
        int right = getTotalNumNode(root.rightchild);
        return left + right + 1;
    }
View Code

  求二叉树种的叶子节点的个数

// 求二叉树中叶子节点的个数
    int getNumForLeafNode(Node root) {
        if (root == null) {
            return 0;
        }
        if (root.leftchild == null && root.rightchild == null) {
            return 1;
        }
        return getNumForLeafNode(root.leftchild) + getNumForLeafNode(root.rightchild);
    }
View Code

  求二叉树中第k层节点的个数

 //求二叉树中第k层节点的个数
    int getNumOfkLevelNode(Node root, int k) {
        if (root == null || k < 1) {
            return 0;
        }
        if (k == 1) {
            return 1;
        }
        int numsLeft = getNumOfkLevelNode(root.leftchild, k - 1);
        int numsRight = getNumOfkLevelNode(root.rightchild, k - 1);
        return numsLeft + numsRight;
    }
View Code

  两个二叉树是否完全相同

boolean isSameTree(Node t1, Node t2) {
        if (t1 == null && t2 == null) {
            return true;
        } else if (t1 == null || t2 == null) {
            return false;
        }
        if(!t1.getValue().equals(t2.getValue())){
            return false;
        }
        boolean left = isSameTree(t1.leftchild, t2.leftchild);
        boolean right = isSameTree(t1.rightchild, t2.rightchild);
        return left && right;
    }
View Code

  两个二叉树是否互为镜像

//两个二叉树是否互为镜像
    boolean isMirror(Node t1, Node t2) {
        if (t1 == null && t2 == null) {
            return true;
        }
        if (t1 == null || t2 == null) {
            return false;
        }
        if (!t1.getValue().equals(t2.getValue())) {
            return false;
        }
        return isMirror(t1.leftchild, t2.rightchild) && isMirror(t1.rightchild, t2.leftchild);
    }
View Code

  翻转/镜像二叉树

    // 翻转二叉树or镜像二叉树
    Node mirrorTreeNode(Node root) {
        if (root == null) {
            return null;
        }
           Node left = mirrorTreeNode(root.leftchild);
        Node right = mirrorTreeNode(root.rightchild);
        root.leftchild = right;
        root.rightchild = left;
        return root;
    }
View Code

 前根序遍历 父节点->左孩子->右孩子

// 前根序遍历 父节点->左孩子->右孩子
    public void preOrder(Node no) {
        if (no != null) {
            printValue(no);
            preOrder(no.getLeftchild());
            preOrder(no.getRightchild());
        }
    }
View Code

中根序遍历  左孩子->父节点->右孩子

//中根序遍历  左孩子->父节点->右孩子
    public void inOrder(Node no) {
        if (no != null) {
            inOrder(no.getLeftchild());
            printValue(no);
            inOrder(no.getRightchild());
        }
    }
View Code

 后根序遍历 左孩子->右孩子->父节点

// 后根序遍历 左孩子->右孩子->父节点
    public void reOrder(Node no) {
        if (no == null) {
            return;
        }
        reOrder(no.getLeftchild());
        reOrder(no.getRightchild());
        printValue(no);
    }
View Code

二叉查找树(Binary Sort Tree)(排序树/搜索树)

二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
  1) 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  2) 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
  3) 左、右子树也分别为二叉排序树;
  4) 没有键值相等的节点。

二叉查找树的性质:

     对二叉查找树进行中序遍历,即可得到有序的数列
二叉查找树的时间复杂度

     它和二分查找一样,插入和查找的时间复杂度均为O(logn),但是在最坏的情况下仍然会有O(n)的时间复杂度。原因在于插入和删除元素的时候,树没有保持平衡。我们追求的是在最坏的情况下仍然有较好的时间复杂度,这就是平衡查找树设计的初衷

 

平衡二叉查找树(Balanced Binary Tree)(简称平衡二叉树)(avl树):

          它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树

平衡因子(bf):

     结点的左子树的深度减去右子树的深度,那么显然-1<=bf<=1

查询 & 插入 & 删除 时间复杂度:O(logn)

应用:

  • windows对进程地址空间的管理用到了AVL树
判断是否为平衡二叉树 
// 判断是否为平衡二叉树
    public boolean isBalanced(Node root, int depth) {
        if (root == null) {
            depth = 0;
            return true;
        }
        int leftDepth = 0;
        int rigthDepth = 0;

        boolean isLeftBalanced = isBalanced(root.getLeftchild(), leftDepth);
        boolean isRightBalanced = isBalanced(root.getLeftchild(), rigthDepth);

        if (isLeftBalanced && isRightBalanced) {
            int diff = leftDepth - rigthDepth;
            
            if (diff <= 1 || diff >= -1) {
                depth = leftDepth > rigthDepth ? leftDepth : rigthDepth + 1;
                return true;
            }
        }
        return false;
    }
View Code
 
红黑树:
是一种近似平衡的二叉查找树,它能够确保任何一个节点的左右子树的高度差不会超过二者中较低那个的一陪。具体来说,红黑树是满足如下条件的二叉查找树(binary search tree):
  • 节点是红色或黑色
  • 根是黑色
  • 所有叶子都是黑色(叶子是NIL节点)
  • 从每个叶子到根的所有路径上不能有两个连续的红色节点
  • 从任一节点到其每个叶子的所有简单路径 都包含相同数目的黑色节点

查询 & 插入 & 删除 时间复杂度:O(logn)

应用:

  • 广泛用在C++的STL中。如map和set都是用红黑树实现的
  • 著名的linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块
  • epoll在内核中的实现,用红黑树管理事件块
  • nginx中,用红黑树管理timer等
  • Java的TreeMap实现

 红黑树 vs avl树

  • 插入:如果插入一个node引起了树的不平衡,AVL和RB-Tree都是最多只需要2次旋转操作,即两者都是O(1);
  • 删除:最坏情况下,AVL需要维护从被删node到root这条路径上所有node的平衡性,因此需要旋转的量级O(logN),而RB-Tree最多只需3次旋转,只需要O(1)的复杂度。AVL的结构相较RB-Tree来说更为平衡,在插入和删除node更容易引起Tree的unbalance,因此在大量数据需要插入或者删除时,AVL需要rebalance的频率会更高。因此,RB-Tree在需要大量插入和删除node的场景下,效率更高。
  • 查找:由于AVL高度平衡,因此AVL的search效率更高

map的实现只是折衷了两者在search、insert以及delete下的效率。总体来说,RB-tree的统计性能是高于AVL的。

 

Trie树(字典树)

B-树(平衡多路查找树)

一棵m阶的b树满足下列条件:

  • 每个结点至多拥有m棵子树根结点至少拥有两颗子树(存在子树的情况下)
  • 除了根结点以外,其余每个分支结点至少拥有 m/2 棵子树
  • 所有的叶结点都在同一层上
  • 有 k 棵子树的分支结点则存在 k-1 个关键码,关键码按照递增次序进行排列
  • 关键字数量需要满足ceil(m/2)-1 <= n <= m-1

b树的特性

  • 关键字集合分布在整颗树中
  • 任何一个关键字出现且只出现在一个结点中
  • 搜索有可能在非叶子结点结束
  • 其搜索性能等价于在关键字全集内做一次二分查找

查询,插入,删除 时间复杂度:O(logn)

B+树

B+树是B-树的变体,也是一种多路搜索树:
       其定义基本与B-树同,除了:

  • 非叶子结点的子树指针与关键字个数相同
  • 非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间)
  • 为所有叶子结点增加一个链指针
  • 所有关键字都在叶子结点出现

 B+的特性:

  • 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的
  • 不可能在非叶子结点命中
  • 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层
  • 更适合文件索引系统

查询,插入,删除 时间复杂度:O(logn)

应用:InnoDB索引 MyISAM索引

B*树

是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针

B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3
(代替B+树的1/2);
       B+树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据
复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父
结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针;
       B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分
数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字
(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之
间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;
       所以,B*树分配新结点的概率比B+树要低,空间使用率更高;

R树

      R树是B树在高维空间的扩展,是一棵平衡树。每个R树的叶子结点包含了多个指向不同数据的指针,这些数据可以是存放在硬盘中的,也可以是存在内存中。根据R树的这种数据结构,当我们需要进行一个高维空间查询时,我们只需要遍历少数几个叶子结点所包含的指针,查看这些指针指向的数据是否满足要求即可。这种方式使我们不必遍历所有数据即可获得答案,效率显著提高。下图1是R树的一个简单实例:

 

posted @ 2018-01-23 18:32  情歌z  阅读(470)  评论(0编辑  收藏  举报