【算法&数据结构体系篇class36】有序表 (中篇)SB树、跳表

这篇具有很好参考价值的文章主要介绍了【算法&数据结构体系篇class36】有序表 (中篇)SB树、跳表。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、SB树(size-balance-tree)

1)让每一个叔叔节点为头的数,节点个数都不少于其任何一个侄子节点

2)也是从底层被影响节点开始向上做路径每个节点检查

3)与AVL树非常像,也是四种违规类型:LLRRLRRL

4)与AVL树非常像,核心点是:

LL(做一次右旋)、RR(做一次左旋)

LRRL(利用旋转让底层那个上到顶部)

5)与AVL树不同的是,每轮经过调整后,谁的孩子发生变化了,谁就再查

二、SB树在使用时候的改进

1)删除时候可以不用检查

2)就把平衡性的调整放在插入的时候

3)因为这种只要变就递归的特性,别的树没有

4)可以在节点上封装别的数据项,来增加功能

 代码演示:

package class36;



/**
 * SB树(size-balance-tree)
 * 1)让每一个叔叔节点为头的数,节点个数都不少于其任何一个侄子节点   (叔叔节点就是  cur的父节点的兄弟节点  就是cur的叔叔节点 而且cur就是其侄子节点)
 * 2)也是从底层被影响节点开始向上做路径每个节点检查
 * 3)与AVL树非常像,也是四种违规类型:LL、RR、LR、RL
 * 4)与AVL树非常像,核心点是:
 * LL(做一次右旋)、RR(做一次左旋)
 * LR和RL(利用旋转让底层那个上到顶部)
 * 5)与AVL树不同的是,每轮经过调整后,谁的孩子发生变化了,谁就再查
 *    LL 调整后   cur.r  cur两个节点的孩子节点发生变化 所以就对两个节点递归进行再次调整
 *    LR    cur.l cur.r cur 三个节点
 *    RR    cur.l cur 两个节点
 *    RL    cur.l cur.r cur 三个节点
 *
 * SB树在使用时候的改进
 * 1)删除时候可以不用检查
 *
 * 2)就把平衡性的调整放在插入的时候
 *
 * 3)因为这种只要变就递归的特性,别的树没有
 *
 * 4)可以在节点上封装别的数据项,来增加功能
 */
public class SizeBalancedTreeMap1 {
    //定义SB树节点类, K类型表示树的 key 支持比较 继承比较类 用于后续的大小比较找对应的节点位置
    public static class SBTNode<K extends Comparable<K>, V> {
        public K k;           //SB树节点的 key值
        public V v;           //SB树节点的 value值
        public SBTNode<K, V> l; //当前节点的左子节点
        public SBTNode<K, V> r; //当前节点的右子节点
        public int size;         //sb树该节点作为根节点的子树的节点个数  作为后续平衡性调整的关键因子

        public SBTNode(K key, V value) {    //构造器 初始化节点属性 键值
            k = key;
            v = value;
            size = 1;               //节点个数为当前节点本身 1
        }
    }

    //定义SB树类  实现有序表的类结构  注意Key值支持比较大小
    public static class SizeBalancedTreeMap<K extends Comparable<K>, V>{
        private SBTNode<K,V> root;            //定义SB树的根节点

        //右旋操作 将cur右旋到 其右子节点  而其左子节点会上移到cur位置
        //注意返回值 是因为要刷新当前节点树的新的头节点 给其他的方法调用返回 刷新
        private SBTNode<K,V> rightRotate(SBTNode<K,V> cur){
            SBTNode<K,V> left = cur.l;         //取出当前节点的左子节点
            cur.l = left.r;                    //left节点的右子树 赋值给 cur节点的左子树
            left.r = cur;                      //left上移 然后将右子树指向 cur节点
            left.size = cur.size;              //先调整 left树的节点个数 就换成cur树的个数
            cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1;  //再调整cur 的个数 因为cur的位置子节点可能调整了所以等Left调整好再改
            return left; //返回新的头节点
        }

        //左旋操作 将cur左旋到 其左子节点  而其右子节点会上移到cur位置
        private SBTNode<K,V> leftRotate(SBTNode<K,V> cur){
            SBTNode<K,V> right = cur.r;         //取出当前节点的右子节点
            cur.r = right.l;                    //right节点的左子树 赋值给 cur节点的右子树
            right.l = cur;                      //right上移 然后将左子树指向 cur节点
            right.size = cur.size;              //先调整 right树的节点个数 就换成cur树的个数
            cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1;  //再调整cur 的个数 因为cur的位置子节点可能调整了所以等right调整好再改
            return right; //返回新的头节点
        }

        //SBT树,调整平衡方法 返回调整后的新头节点
        // 注意这里的平衡 是根据 叔叔节点数 大于 全部每个侄子节点数  否则就是不平衡要调整
        //与AVL树相同分四种情况 LL LR RR RL
        //每一种对应的调整平衡旋转都一样  右旋  ; 对子节点左旋再对当前节点右旋 ; 左旋;  对子节点右旋再对当前节点左旋
        //不一样的地方在于 SBT树还需要再判断那些节点的孩子节点变化了 对这些节点 要递归继续调整平衡,因为可能会调整了树位置后 又多出了一些不平衡的情况
        //   LL 调整后   cur.r  cur两个节点的孩子节点发生变化 所以就对两个节点递归进行再次调整
        //   LR    cur.l cur.r cur 三个节点
        //   RR    cur.l cur 两个节点
        //   RL    cur.l cur.r cur 三个节点
        private SBTNode<K,V> maintain(SBTNode<K,V> cur){
            if(cur == null) return null;                 //cur空树  直接返回null

            //根据平衡特性 叔叔节点与侄子节点的比较  分别取出 cur.l cur.l.l cur.l.r ; cur.r cur.r.l cur.r.r 六个节点进行比较
            //也就是cur的左右子树 以及孙子树
            int leftSize = cur.l != null ? cur.l.size : 0;
            int leftLeftSize = cur.l != null && cur.l.l !=null ? cur.l.l.size : 0;
            int leftRightSize = cur.l != null && cur.l.r != null ? cur.l.r.size : 0;
            int rightSize = cur.r != null ? cur.r.size : 0;
            int rightLeftSize = cur.r != null && cur.r.l != null ? cur.r.l.size :0;
            int rightRightSize = cur.r != null && cur.r.r != null ? cur.r.r.size : 0;

            if(leftLeftSize > rightSize ){    //1.左左侄子树 大于 右叔叔树 LL型
                cur = rightRotate(cur);      //当前节点右旋
                cur.r = maintain(cur.r);     //当前节点的右子节点 其孩子节点变化 需要递归调用再调整看看是否不平衡了
                cur = maintain(cur);         //等右子节点调整好 再调整cur节点 因为其孩子节点肯定也是变化了
            }
            else if(leftRightSize > rightSize){    //2. 左右侄子树 大于 右叔叔树 LR型
                cur.l = leftRotate(cur.l);      //当前节点的左子节点 左旋
                cur = rightRotate(cur);         //当前节点 右旋 把孙子节点提上来
                cur.l = maintain(cur.l);     //当前节点的左子节点 其孩子节点变化 需要递归调用再调整看看是否不平衡了
                cur.r = maintain(cur.r);     //当前节点的右子节点 其孩子节点变化 需要递归调用再调整看看是否不平衡了
                cur = maintain(cur);         //等左右子节点调整好 再调整cur节点 因为其孩子节点肯定也是变化了
            }
            else if(rightRightSize > leftSize ){    //3.右右侄子树 大于 左叔叔树 RR型
                cur = leftRotate(cur);      //当前节点左旋
                cur.l = maintain(cur.l);     //当前节点的左子节点 其孩子节点变化 需要递归调用再调整看看是否不平衡了
                cur = maintain(cur);         //等左子节点调整好 再调整cur节点 因为其孩子节点肯定也是变化了
            }
            else if(rightLeftSize > leftSize){    //4. 右左侄子树 大于 左叔叔树 RL型
                cur.r = rightRotate(cur.r);      //当前节点的右子节点 右旋
                cur = leftRotate(cur);         //当前节点 左旋 把孙子节点提上来
                cur.l = maintain(cur.l);     //当前节点的左子节点 其孩子节点变化 需要递归调用再调整看看是否不平衡了
                cur.r = maintain(cur.r);     //当前节点的右子节点 其孩子节点变化 需要递归调用再调整看看是否不平衡了
                cur = maintain(cur);         //等左右子节点调整好 再调整cur节点 因为其孩子节点肯定也是变化了
            }
            return cur;    //最后要返回刷新的当前新头节点
        }

        //如果插入key  那么就要找要插入的位置 如果SBT树存在 key值的节点 那么就直接返回这个节点 后续就直接修改v值
        //如果不存在 那么就根据搜索顺序 往左右下移 直到空 返回的就是key的父节点
        private SBTNode<K,V> findLastIndex(K key){
            //定义两个辅助变量指向root 进行遍历
            SBTNode<K,V> pre = root;
            SBTNode<K,V> cur = root;
            //开始遍历cur 节点 ,
            while(cur != null){
                //让pre指向cur
                pre = cur;
                //如果key值等于当前cur的key 那么就是找到最后经过的节点 不用再继续下去了  直接退出
                if(key.compareTo(cur.k) == 0){
                    break;
                } else if(key.compareTo(cur.k) < 0){
                    //如果key是比当前cur小的 那么就去cur的左子树找,因为左子树值小 区间就在左子树
                    cur = cur.l;
                }else{
                    //如果key比当前cur大,那么就需要继续到cur的右子树找,找到最靠近key的节点
                    cur =cur.r;
                }
            }
            return pre;  //最后返回辅助变量指向的就是最终的结果
        }

        //获取大于等于指定的key的最靠近的节点 比如key=8  SBT树节点有1,3,6,7,13,10 那么就返回10 最接近8 且大于等于8  如果存在8 就返回8
        private SBTNode<K,V> findLastNoSmallIndex(K key){
            SBTNode<K,V> ans = null;
            SBTNode<K,V> cur = root;
            while(cur != null){
                if(key.compareTo(cur.k) == 0){
                    //key 等于当前cur的key  ans指向当前节点cur 退出
                    ans = cur;
                    break;
                }else if(key.compareTo(cur.k) < 0){
                    //如果key 小于cur的key 说明cur是在目标范围内的,ans赋值当前节点 接着下层到做子树看看有没有更小更接近key的节点
                    ans = cur;
                    cur = cur.l;
                }else{
                    //如果key大于cur 说明cur 不在范围内,需要往右子树 扩大到 大于等于key值
                    cur = cur.r;
                }
            }
            return ans;  //最后返回ans 节点
        }

        //获取小于等于指定的key的最靠近的节点 比如key=8  SBT树节点有1,3,6,7,13,10 那么就返回7 最接近8 且小于等于8  如果存在8 就返回8
        private SBTNode<K,V> findLastNoBigIndex(K key){
            SBTNode<K,V> ans = null;
            SBTNode<K,V> cur = root;
            while(cur != null){
                if(key.compareTo(cur.k) == 0){
                    ans = cur;
                    break;
                } else if(key.compareTo(cur.k) > 0){  //key 大于 cur.k 说明是在范围内 赋值ans 并且接着往右子树扩大看有没有更接近的节点
                    ans = cur;
                    cur = cur.r;
                } else{
                    cur = cur.l;
                }
            }
            return ans;
        }

        //添加一个K,V值,传入的root节点从整个SBT树的根节点开始往下遍历
        //因为添加的过程可能会需要调整平衡性,并且会不断往最低个下层 找到位置添加 然后再返回新调整好的节点返回上层 就需要递归
        //为了方便 我们递归函数给一个 返回当前节点的返回值
        private SBTNode<K,V> add(SBTNode<K,V> cur, K key, V value){
            if(cur == null){
                //如果是一个空的树,那么添加的节点 就直接返回一个新节点就可以了
                return new SBTNode<>(key,value);
            } else{
                //添加元素 个数要加
                cur.size++;
                //如果非空,那么就判断大小 如果key 比cur的大 那么就需要cur.r 去递归右子树 否则就是递归左子树 并且要返回接收刷新节点左右子树
                //这里没有等于的情况,因为会在 调用添加节点的 put方法去提前做相等的判断
                if(key.compareTo(cur.k) > 0){
                    cur.r = add(cur.r,key,value);
                }else{
                    cur.l = add(cur.l,key,value);
                }

                //然后再调整cur的平衡性
                return maintain(cur);
            }
        }

        //在cur头节点的SBT树上,删掉key的对应节点 , 返回树的新头节点,因为可能会涉及平衡调整 ,头节点需要返回
        private SBTNode<K,V> delete(SBTNode<K,V> cur, K key){
            cur.size--;   //节点个数上来先减
            if(key.compareTo(cur.k) > 0){
                //key 大于 cur 那么就递归 cur右子树 并且返回新的右子树头节点
                cur.r = delete(cur.r, key);
            } else if(key.compareTo(cur.k) < 0){
                //key 小于 cur 递归cur左子树
                cur.l = delete(cur.l, key);
            } else{ //相等值,那么就表示要删的节点找到了
                if(cur.l == null && cur.r == null){
                    cur = null;    //如果没有左右子节点 直接删除 cur赋值空
                } else if(cur.l != null && cur.r == null){  //左非空 右空 直接将左子节点上移
                    cur = cur.l;
                } else if(cur.r != null && cur.l == null){  //右非空 左空 右子节点上移
                    cur = cur.r;
                } else {
                    //左右子节点非空,那么删除cur节点 就需要找后继节点 替换cur 也就是cur的右子树的最左子节点,这个节点就是接着比cur大的最接近的下个节点
                    //思路就是 先在cur.r子树中 找到最左子节点  然后把该节点  提出到cur节点
                    //而最左子节点 也可能有右子树 那么就把这个右子树挂到 最左子节点的父节点的左子树上即可
                    SBTNode<K,V> pre = null;              //pre赋值变量 用来获取cur的右子树的最左子节点的父节点
                    SBTNode<K,V> next = cur.r;            //当前节点的右子树
                    next.size--;                          //由于 右子树最左子节点要提到cur位置 所以每个上层节点的大小要依次-1 这里先进行-1操作
                    while(next.l != null){
                        pre = next;    //把next赋值给pre 到最后跳出时 next来到最左子节点 而pre就是其父节点
                        next = next.l; //下沉到cur右子树的最左节点 直到为空停 next就是最左子节点
                        next.size--;   //每个下节点的大小都需要-1 因为最底部的左子节点将被提取
                    }

                    if(pre != null){  //跳出循环 如果说最左节点的存在父节点
                        pre.l = next.r;   //此时next最左子节点提上去后  其右子树 就指向父节点的左子树位置  也就是顶替上去之前的最左子节点
                        next.r = cur.r;   //并且将最左子节点的右指向 指到当前节点的右指向 进行替换cur位置
                    }
                    //如果说pre为空 说明cur.r 右子树 只有一个节点。 该节点就充当最左子节点 上移到cur 那么其右子树自然是指向null 就不用进行指向cur.r
                    next.l = cur.l;      //最后这里 再将cur的左子树 指向 next最左子节点的左子树 替换cur
                    next.size = next.l.size + (next.r == null ? 0 : next.r.size) + 1;  //刷新next节点的个数
                    cur = next;                     //最后cur节点再指向新的节点  完成cur的替换并且删除了cur
                }
            }
            return cur;    //最后返回cur新的头节点 这里就不需要再调用平衡调整方法 SBT的特别之处就在于此 会在add的时候快速调整整个树的平衡 效率一样都是高效的 logN 因为最多就是一个高度的往上调整
        }


        //在cur头节点的SBT树上,查询 key为 kth的所在节点   假设kth = 3,表示要当前树上第3小的节点
        private SBTNode<K,V> getIndex(SBTNode<K,V> cur, int kth){
            //注意要判断cur.l是否为空 避免空指针异常
            if(kth == (cur.l != null ? cur.l.size : 0) + 1){
                //1.假设当前头节点cur,左树上2个节点,就中了第一个if, 3 = 2+1 左树上2个节点,右数上又都是比cur大的节点,所以cur正好是第3个。于是直接返回。
                return cur;
            } else if(kth <= (cur.l != null ? cur.l.size : 0)){
                //2.假设当前头节点cur,左树上5个节点,就中了第2个if,3 < 5,左树上5个节点,一定包含第3小的节点,所以在左树上找第3小的节点。
                return getIndex(cur.l,kth);
            } else {
                //3.假设当前头节点cur,左树上1个节点。就中了第3个if,左树上1个节点,头节点1个节点,这一共才凑够前两小,那么第3小的节点呢?一定是右树上第1小的节点。 所有就传参要减去左数个数 再减当前节点1
                return getIndex(cur.r, kth - (cur.l != null ? cur.l.size : 0) - 1);
            }
        }

        //外部接口: 获取整个树的节点个数
        public int size(){
            return root == null ? 0 : root.size;
        }

        //外部接口: 判断key值是否包含在SBT树中
        public boolean containsKey(K key){
            if(key == null){
                //如果key是空值 就手动抛个异常返回
                throw new RuntimeException("invalid parameter.");
            }
            //调用方法查询key对应的节点位置 如果key存在 那么node就是对应的key节点 返回true 否则返回false
            SBTNode<K,V> node = findLastIndex(key);
            return node != null && key.compareTo(node.k) == 0 ;
        }

        //外部接口: 新增k,v节点 put   如果存在同个key 就将value刷新即可
        public void put(K key, V value){
            if(key == null){  //空的key  直接抛出异常
                throw new RuntimeException("invalid parameter.");
            }
            SBTNode<K,V> node = findLastIndex(key); //获取key对应的在树中的节点
            if(node != null && key.compareTo(node.k) == 0){
                node.v = value;    //如果存在key对应的节点 ,就刷新节点的value值
            }else{
                root = add(root,key,value);   //如果不存在 那么就在整个树 添加新节点 并且返回新的头节点 赋值给root
            }
        }

        //外部接口: 移除key节点 如果存在key节点的话
        public void remove(K key){
            if(key == null) return;
            if(containsKey(key)){  //如果存在该节点  则调用删除方法进行删除节点 然后root接收新的头节点
                root = delete(root,key);
            }
        }

        //外部接口: 获取树对应的索引的节点的key  索引是从0开始的 注意 所以key=0 表示要取树中第一个节点 第一小的节点 也就是树的最左子节点
        public K getIndexKey(int index){
            if(index < 0 || index >= size()){
                throw new RuntimeException("invalid parameter.");
            }
            return getIndex(root,index + 1).k;  //index索引范围合理 就调用方法返回节点的k值 注意index要+1 因为该方法是 从1开始的 最小
        }

        //外部接口: 获取索引对应节点的value
        public V getIndexValue(int index){
            if(index < 0 || index >= size()){
                throw new RuntimeException("invalid parameter.");
            }
            return getIndex(root,index+1).v;    //调用方法返回节点的v值 index索引要+1
        }

        //外部接口: 获取key 节点对应的value
        public V get(K key){
            if(key == null){
                throw new RuntimeException("invalid parameter.");
            }
            SBTNode<K,V> node = findLastIndex(key);  //获取key 对应节点
            if(node !=null && key.compareTo(node.k) == 0){
                return node.v;   //如果对应节点找到 与key值相等 就返回该节点的value
            } else{
                return null;      //如果没有找到 返回null
            }
        }

        //外部接口: 获取树中的第1个Key  SBT树中的最左子节点 就是第一个节点
        public K firstKey(){
            if(root == null) return null;    //空树 返回null
            SBTNode<K,V> node = root;        //节点赋值root
            while (node.l != null){          //往左节点遍历直到空 node就会来到最左子节点
                node = node.l;
            }
            return node.k;                   //返回其节点的key
        }

        //外部接口: 获取树中的最后1个Key  SBT树中的最右子节点
        public K lastKey(){
            if(root == null) return null;    //空树 返回null
            SBTNode<K,V> node = root;        //节点赋值root
            while (node.r != null){          //往右节点遍历直到空 node就会来到最右子节点
                node = node.r;
            }
            return node.k;                   //返回其节点的key
        }

        //外部接口: 获取树中 小于等于key的节点 最大的节点 也就是就接近key的节点
        public K floorKey(K key){
            if(key == null){
                throw new RuntimeException("invalid parameter.");
            }
            //调用小于等于方法 返回节点 非空则为小于等于key的最大的那个节点 返回其key
            SBTNode<K,V> node = findLastNoBigIndex(key);
            return node != null ? node.k : null;
        }

        //外部接口: 获取树中 大于等于key的节点 最小的节点 也就是就接近key的节点
        public K ceilingKey(K key){
            if(key == null){
                throw new RuntimeException("invalid parameter.");
            }
            //调用大于等于方法 返回节点 非空则为大于等于key的最小的那个节点 返回其key
            SBTNode<K,V> node = findLastNoSmallIndex(key);
            return node != null ? node.k : null;
        }
    }

    // for test
    public static void printAll(SBTNode<String, Integer> head) {
        System.out.println("Binary Tree:");
        printInOrder(head, 0, "H", 17);
        System.out.println();
    }

    // for test
    public static void printInOrder(SBTNode<String, Integer> head, int height, String to, int len) {
        if (head == null) {
            return;
        }
        printInOrder(head.r, height + 1, "v", len);
        String val = to + "(" + head.k + "," + head.v + ")" + to;
        int lenM = val.length();
        int lenL = (len - lenM) / 2;
        int lenR = len - lenM - lenL;
        val = getSpace(lenL) + val + getSpace(lenR);
        System.out.println(getSpace(height * len) + val);
        printInOrder(head.l, height + 1, "^", len);
    }

    // for test
    public static String getSpace(int num) {
        String space = " ";
        StringBuffer buf = new StringBuffer("");
        for (int i = 0; i < num; i++) {
            buf.append(space);
        }
        return buf.toString();
    }

    public static void main(String[] args) {
        SizeBalancedTreeMap<String, Integer> sbt = new SizeBalancedTreeMap<String, Integer>();
        sbt.put("d", 4);
        sbt.put("c", 3);
        sbt.put("a", 1);
        sbt.put("b", 2);
        // sbt.put("e", 5);
        sbt.put("g", 7);
        sbt.put("f", 6);
        sbt.put("h", 8);
        sbt.put("i", 9);
        sbt.put("a", 111);
        System.out.println(sbt.get("a"));
        sbt.put("a", 1);
        System.out.println(sbt.get("a"));
        for (int i = 0; i < sbt.size(); i++) {
            System.out.println(sbt.getIndexKey(i) + " , " + sbt.getIndexValue(i));
        }
        printAll(sbt.root);
        System.out.println(sbt.firstKey());
        System.out.println(sbt.lastKey());
        System.out.println(sbt.floorKey("g"));
        System.out.println(sbt.ceilingKey("g"));
        System.out.println(sbt.floorKey("e"));
        System.out.println(sbt.ceilingKey("e"));
        System.out.println(sbt.floorKey(""));
        System.out.println(sbt.ceilingKey(""));
        System.out.println(sbt.floorKey("j"));
        System.out.println(sbt.ceilingKey("j"));
        sbt.remove("d");
        printAll(sbt.root);
        sbt.remove("f");
        printAll(sbt.root);

    }
}
111
1
a , 1
b , 2
c , 3
d , 4
f , 6
g , 7
h , 8
i , 9
Binary Tree:
                                       v(i,9)v     
                      v(h,8)v     
                                       ^(g,7)^     
     H(f,6)H     
                                       v(d,4)v     
                      ^(c,3)^     
                                                        v(b,2)v     
                                       ^(a,1)^     

a
i
g
g
d
f
null
a
i
null
Binary Tree:
                                       v(i,9)v     
                      v(h,8)v     
                                       ^(g,7)^     
     H(f,6)H     
                      ^(c,3)^     
                                                        v(b,2)v     
                                       ^(a,1)^     

Binary Tree:
                                       v(i,9)v     
                      v(h,8)v     
     H(g,7)H     
                      ^(c,3)^     
                                                        v(b,2)v     
                                       ^(a,1)^     


Process finished with exit code 0

三、跳表(skiplist)

1)结构上根本和搜索二叉树无关

2)利用随机概率分布来使得高层索引可以无视数据规律,做到整体性能优良

3)思想是所有有序表中最先进的

4结构简单就是多级单链表

代码演示:文章来源地址https://www.toymoban.com/news/detail-438150.html

package class36;

import java.util.ArrayList;
import java.util.Currency;

/**
 * 跳表(skiplist)
 * 1)结构上根本和搜索二叉树无关
 *
 * 2)利用随机概率分布来使得高层索引可以无视数据规律,做到整体性能优良
 *
 * 3)思想是所有有序表中最先进的
 *
 * 4)结构简单就是多级单链表
 */
public class SkipListMap1 {
    //跳表的节点定义
    public static class SkipListNode<K extends Comparable<K>, V>{
        //节点的键值   以及一个下个指向的链表集合
        public K k;
        public V v;
        public ArrayList<SkipListNode<K,V>> nextNodes;

        public SkipListNode(K key, V value){
            k = key;
            v = value;
            nextNodes = new ArrayList<SkipListNode<K,V>>();
        }

        // 遍历的时候,如果是往右遍历到的null(next == null), 遍历结束
        // 头(null), 头节点的null,认为最小
        // node  -> 头,node(null, "")  node.isKeyLess(!null)  true
        // node里面的key是否比otherKey小,true,不是false
        public boolean isKeyLess(K otherKey){
            //otherkey非空 k 空 返回true  表示空值是最小的
            //两者非空 那么k 小于 otherkey就返回true
            return otherKey != null && (k == null || k.compareTo(otherKey) < 0);
        }

        //判断key值 是否与当前的节点的k值相等   都为空 则判断相等
        public boolean isKeyEqual(K key){
            return (k == null && key == null) || (k != null && key != null && k.compareTo(key) == 0);
        }
    }

    //定义跳表类 实现有序表
    public static class SkipListMap<K extends Comparable<K>, V>{
        private SkipListNode<K,V> head;        //定义跳表的头节点 第一个节点
        private final double P = 0.5;   //p概率 用于后续进行随机概率判断节点的层级  <0.5就增加层级 否则就不做
        private int size;               //跳表的节点个数
        private int maxLevel;           //跳表的最高层级 第一层是 0, 第二层1 ...

        public SkipListMap(){           //构造器初始化
            head = new SkipListNode<>(null,null);
            head.nextNodes.add(null);   //注意这里头节点的 下层指向集合 可以初始化时添加一个 null
            size = 0;
            maxLevel = 0;
        }

        // 在level层里,如何往右移动
        // 现在来到的节点是cur,来到了cur的level层,在level层上,找到<key最后一个节点并返回
        private SkipListNode<K,V> mostRightLessNodeInLevel(K key, SkipListNode<K,V> cur, int level){
            SkipListNode<K,V> next = cur.nextNodes.get(level);     //获取当前节点cur 在level层上的  指向节点
            while (next != null && next.isKeyLess(key)){           //遍历next下个节点 非空并且其k值小于目标节点key 就继续往下个节点走
                cur = next;
                next = cur.nextNodes.get(level);
            }
            return cur;          //最后跳出循环 cur就是小于key 最右的一个节点
        }

        // 从最高层开始,一路找下去,
        // 最终,找到第0层的<key的最右的节点
        private SkipListNode<K,V> mostRightLessNodeInTree(K key){
            if(key == null) return null;    //如果查询的key是 null 那么就直接返回null
            int level = maxLevel;           //获取当前跳表的层级高度
            SkipListNode<K,V> node = head;  //获取当前跳表的头节点 用来遍历

            while(level >= 0){              //从最高层级开始往下层走到底0层位置
                node = mostRightLessNodeInLevel(key,node,level--);    //调用方法 当前头节点开始 最高层级 往下指向下一个层级的对应节点返回 层级-- 一直迭代
            }
            return node;    //最后跳出循环 就在0层了 node就是 <key的最右节点
        }

        //外部接口:判断 跳表中是否存在key值的节点
        public boolean containsKey(K key){
            if(key == null) return false;
            SkipListNode<K,V> node = mostRightLessNodeInTree(key);    //找到小于key 最右的节点
            SkipListNode<K,V> cur = node.nextNodes.get(0);            //最右节点的下个节点 get(0) 如果值为key 非空 那么就表示存在
            return cur != null && cur.isKeyEqual(key);      //非空 且 值相等 cur 就是其节点 返回true
        }

        //外部接口: 新增节点到跳表上 如果该key存在 那就进行进行 不存在就进行插入
        public void put(K key, V value){
            if(key == null) return;

            //调用方法 找到整个树上 <key 离key最近的节点
            SkipListNode<K,V> pre = mostRightLessNodeInTree(key);
            //获取下个指向集合在第一个 就是来到了key位置
            SkipListNode<K,V> node = pre.nextNodes.get(0);
            if(node != null && node.isKeyEqual(key)){
                //如果找到的节点非空 并且k值等于key 那么说明树中存在key值节点 就进行修改value
                node.v = value;
            } else{
                //如果不存在 那么就新增节点  树的个数加1
                size++;
                //然后用随机概率判断 节点所占有的层级高度
                int newNodeLevel = 0;
                while (Math.random() < P){
                    //如果概率小于P 那么就进行层级增加
                    newNodeLevel++;
                }
                //然后需要判断 如果新节点高度 比当前跳表最大高度还大 那就需要先把最大高度追加到相等 并且把下个指向集合追加null节点
                while (newNodeLevel > maxLevel){
                    head.nextNodes.add(null);
                    maxLevel++;

                }

                //层级高度确认好之后 对应创建新节点
                SkipListNode<K,V> newNode = new SkipListNode<>(key,value);
                for(int i = 0; i <= newNodeLevel; i++){
                    newNode.nextNodes.add(null); //首先对节点的下指向做初始化 高度多少 指向就有多少个
                }

                //接着就开始进行从最高层遍历到0层
                int level = maxLevel;
                SkipListNode<K,V> cur = head;  //cur指向头节点
                while (level >= 0){
                    //从最高层开始往下遍历 找到当前层级对应 <key 最右的节点
                    cur = mostRightLessNodeInLevel(key,cur,level);

                    //注意当最高层级遍历 是来到了要插入节点的层级时,才表示节点需要依次对每一层进行插入
                    //比如最高高度一开始8, 节点随机高度后时10 那么maxlevel就会先升到同等高度 10
                    //此时情况 就是每一层都是有节点值 就会进判断做插入
                    //比如maxLevel=8  newNodeLevel=4 说明新节点层级只会出现在 4 3 2 1 0 层
                    //所以就只会当maxLevel 减到 4的时候 后面的层级才会进判断 对每一层做节点插入
                    if(level <= newNodeLevel){
                        //先将新节点的下个指向  设置为 前一个节点cur的下个指向 都是在level层级的指向  因为节点就是插入 cur --> next 这个中间
                        newNode.nextNodes.set(level,cur.nextNodes.get(level));
                        //再将前一个节点cur 下个指向节点 设置为新节点  就完成了当前level层级 节点的插入
                        cur.nextNodes.set(level,newNode);
                    }
                    level--;   //然后层级--来到下个层级 直到0层
                }
            }
        }

        //外部接口: 获取跳表上的key对应的值
        public V get(K key){
            if(key == null) return null;
            //找到跳表树中 <key 最右的节点pre
            SkipListNode<K,V> pre = mostRightLessNodeInTree(key);
            //pre 的下个指向集合的第一个元素 Node   就是key的位置
            SkipListNode<K,V> node = pre.nextNodes.get(0);
            //如果节点非空 且节点k值等于Key值 说明存在这个key对应节点 返回其v值 否则就是null
            return node != null && node.isKeyEqual(key) ? node.v : null;

        }

        //外部接口: 删除跳表上的key对应的节点
        public void remove(K key){
            //如果包含key值的节点  再进行删除动作
            if(containsKey(key)){
                size--;             //跳表节点数-1
                int level = maxLevel;    //跳表最高层级高度  用来从高往底层依次删除key对应的节点
                SkipListNode<K,V> pre = head;        //头节点
                while(level >= 0 ){
                    pre = mostRightLessNodeInLevel(key,pre,level);     //获取当前pre节点 所在level层级 <key的最右节点
                    SkipListNode<K,V> node = pre.nextNodes.get(level);     //获取pre下个指向节点 node就是key位置
                    if(node != null && node.isKeyEqual(key)){          //如果节点非空 且key值相等 说明这个节点就是key对应节点
                        pre.nextNodes.set(level,node.nextNodes.get(level));    //将pre前一个节点指向 跳过node 来到node的下个节点
                    }
                    //这里还需要注意 如果删除后 当前层 节点都空了 只剩下一个head 节点 也就是pre是head节点 那这一层就要删除 并且层级要-1
                    if(level != 0 && pre == head && pre.nextNodes.get(level) == null){
                        head.nextNodes.remove(level);
                        maxLevel--;    //最高层级-1
                    }
                    level--;    //当前层处理完之后要-1
                }
            }
        }

        //外部接口: 获取跳表的第一个key值  也就是第0层 head头节点的下个节点
        public K firstKey(){
            return head.nextNodes.get(0) != null ? head.nextNodes.get(0).k : null;
        }

        //外部接口: 获取跳表的最后一个key值 依次从高层的指向节点跳转到最后一个节点 这样的方式 时间复杂度才能O(logN)
        public K lastKey(){
            int level = maxLevel;          //跳表最高层级高度
            SkipListNode<K,V> cur = head; //头节点开始去找下个指向节点
            while (level >= 0){
                SkipListNode<K,V> next = cur.nextNodes.get(level);  //节点在level层级的下个指向
                while (next != null){                           //next非空 就继续往右指向
                    cur = next;
                    next = cur.nextNodes.get(level);
                }
                level--;     //层级-- 只要最后第0层
            }
            return cur.k;    //最后返回cur节点 就是最后节点
        }

        //外部接口: 获取 大于等于key 最小的节点
        public K ceilingKey(K key){
            if(key == null) return null;
            SkipListNode<K,V> pre = mostRightLessNodeInTree(key);   //获取<key 最右节点
            SkipListNode<K,V> node = pre.nextNodes.get(0);          //获取下个指向 因为当前来到的是最后第0层 所以就是0索引 node就是key位置
            return node != null ? node.k : null;                    //node就是大于等于key的第一个位置 非空 就直接返回即可 如果是空 那么就表示不存在 返回null
        }

        //外部接口: 获取 小于等于key 最大的节点
        public K floorKey(K key){
            if(key == null) return null;
            SkipListNode<K,V> pre = mostRightLessNodeInTree(key);   //获取<key 最右节点
            SkipListNode<K,V> node = pre.nextNodes.get(0);          //获取下个指向 因为当前来到的是最后第0层 所以就是0索引 node就是key位置
            return node != null && node.isKeyEqual(key)? node.k : pre.k;                    //如果node K值相等 那么最右节点就是node 否则就是pre
        }

        //外部接口: 获取跳表的节点个数
        public int size(){
            return size;
        }

        // for test
        public static void printAll(SkipListMap<String, String> obj) {
            for (int i = obj.maxLevel; i >= 0; i--) {
                System.out.print("Level " + i + " : ");
                SkipListNode<String, String> cur = obj.head;
                while (cur.nextNodes.get(i) != null) {
                    SkipListNode<String, String> next = cur.nextNodes.get(i);
                    System.out.print("(" + next.k + " , " + next.v + ") ");
                    cur = next;
                }
                System.out.println();
            }
        }

        public static void main(String[] args) {
            SkipListMap<String, String> test = new SkipListMap<>();
            printAll(test);
            System.out.println("======================");
            test.put("A", "10");
            printAll(test);
            System.out.println("======================");
            test.remove("A");
            printAll(test);
            System.out.println("======================");
            test.put("E", "E");
            test.put("B", "B");
            test.put("A", "A");
            test.put("F", "F");
            test.put("C", "C");
            test.put("D", "D");
            printAll(test);
            System.out.println("======================");
            System.out.println(test.containsKey("B"));
            System.out.println(test.containsKey("Z"));
            System.out.println(test.firstKey());
            System.out.println(test.lastKey());
            System.out.println(test.floorKey("D"));
            System.out.println(test.ceilingKey("D"));
            System.out.println("======================");
            test.remove("D");
            printAll(test);
            System.out.println("======================");
            System.out.println(test.floorKey("D"));
            System.out.println(test.ceilingKey("D"));


        }



    }


}

Level 0 : 
======================
Level 0 : (A , 10) 
======================
Level 0 : 
======================
Level 2 : (C , C) 
Level 1 : (A , A) (C , C) (D , D) 
Level 0 : (A , A) (B , B) (C , C) (D , D) (E , E) (F , F) 
======================
true
false
C
F
D
D
======================
Level 2 : (C , C) 
Level 1 : (A , A) (C , C) 
Level 0 : (A , A) (B , B) (C , C) (E , E) (F , F) 
======================
C
E

Process finished with exit code 0

到了这里,关于【算法&数据结构体系篇class36】有序表 (中篇)SB树、跳表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】--oj_合并两个有序链表(详解)

    目录 方法一:无头结点的方法  方法二:有头结点的方法 题述: 已给函数头: struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) 已给出链表的结构体定义: struct ListNode {     struct ListNode* next;     int val; }; 已知l1、l2分别指向两个链表 要求: 将两个升序链表合并为一

    2024年02月07日
    浏览(56)
  • 将两个递增的有序链表合并为一个递增的有序链表.【数据结构】【线性表】

    编写一个函数完成如下功能:将两个递增的有序链表合并为一个递增的有序链表。要求结果链表仍使用原来的链表空间,不另外占用其他的存储空间。表中不允许有重复的数据。 要求,在主函数中调用上面的函数测试。 提示:还需要定义其他函数,比如初始化链表,构造单

    2024年02月06日
    浏览(46)
  • 【数据结构OJ题】删除有序数组中的重复项

    原题链接:https://leetcode.cn/problems/remove-duplicates-from-sorted-array/ 目录 1. 题目描述 2. 思路分析 3. 代码实现 用 双指针算法, 定义两个变量src和dst,一开始让src和dst指向num[ ]数组的第一个元素,再使用if语句判断。 如果nums[src]==nums[dst],就让src指向下一位,即src++。如果nums[src]!=

    2024年02月14日
    浏览(39)
  • 企业级大数据体系结构

    作者:禅与计算机程序设计艺术 企业级大数据是指超大规模数据的集合,是管理者、分析师、决策者所需要分析和处理的一种信息资源。基于海量数据的复杂性及其多样性,实现数据可视化、数据挖掘、机器学习等数据处理功能的大数据平台也逐渐成为行业关注热点。因此,

    2024年02月06日
    浏览(45)
  • 【3.1数据库系统】数据库体系结构

    ①.外模式: 外模式面向具体的应用程序,定义在逻辑模式之上,但独立于存储模式和存储设备。 ②.概念模式(模式): 用于描述数据库的逻辑结构。 ③.内模式: 也称为存储模式或物理模式,是数据库内部的一种表示方式,用于描述数据物理结构和存储结构。 ①. 基本关

    2024年01月23日
    浏览(52)
  • 数据结构c语言版:顺序表oj题练习(原地移除元素、合并两个有序数组)

    在单数组里面历遍找val,如果是val,就删除。不是就跳过。 时间复杂度O(n^2),最坏情况每个都是val。相当于一个等差数列。 比如 下标0开始找,0不是,不动数组 下标1,1不是,不动数组 下标2,2是,删除元素,变成【0,1,2,3,0,4,2】 下标2,2是,删除元素,变成【0,

    2024年01月23日
    浏览(63)
  • 异地容灾系统和数据仓库系统设计和体系结构

    ( 1)生产系统数据同步到异地容灾系统 生产系统与异地容灾系统之间是通过百兆网连接的;生产系统的数据库是 Oracle 9i RAC,总的数据量大约为 3 TB,涉及五千多张表。对这些表进行分析归 类,发现容灾系统真正需要实时同步的表大约只有五百张,数据量约为 1 TB,只 要能

    2024年02月09日
    浏览(44)
  • mysql从入门到放弃之数据库体系结构与管理

    第一篇文章中主要学习了mysql二进制的基本安装及数据库初始化等操作,本篇文章主要了解mysql的体系结构和管理,例如: mysql的实例组成、逻辑存储结构、物理存储结构等方面展开学习 提示:以下是本篇文章正文内容,下面案例可供参考 3.1、mysqld守护进程结构 3.2、 引入sql语句

    2024年01月21日
    浏览(48)
  • 数据结构2.2,将两个非递减的有序链表合并为一个非递增的有序链表,要求结果链表仍使用原来两个链表的存储空间,不占用其他的存储空间。表中允许有重复的数据。

    大概思路:1.先写出建立链表的函数(creatlist):分配头节点,尾指针置空。 2.写出插入节点的代码函数:申请一片空间存放要插入的节点,把新插入的节点置空,令指向链表的头节点的下一个指针指向该节点,在把该指针指向新插入的节点。用if函数写出当输入的指小于零时

    2024年02月05日
    浏览(44)
  • PGSQL(PostgreSQL)数据库基础篇:PostgreSQL 的 主要优点 、 劣势 、体系结构 、核心功能 、安装教程。

    1.维护者是PostgreSQL Global Development Group,首次发布于1989年6月。 2.操作系统支持WINDOWS、Linux、UNIX、MAC OS X、BSD。 3.从基本功能上来看,支持ACID、关联完整性、数据库事务、Unicode多国语言。 4.表和视图方面,PostgreSQL支持临时表,而物化视图,可以使用PL/pgSQL、PL/Perl、PL/Python或其

    2024年04月26日
    浏览(61)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包