转自木白 blog 关于java的数据结构(5)

 链表有一个严重的缺陷:需要顺序扫描来定位所查找的元素。查找从表头开始,若成功,则停在被查元素所在的位置,或是在没有找到这个元素时到达表尾。表中元素有序时可以加快查找速度,但仍需顺序查找。因此我们开始构思一种允许跳过某些节点以避免顺序操作的表。跳转表(skip list)是一种有趣的有序链表的变体,它可以使这种非顺序查找成为可能。

       跳转表的结构如下图所示:

 

 

图一

       上图这只不过是一种可能的节点的结构,它的结构是很不规律的。原因一下说明,这跟链表的构造有关系。

       从上图可以看出,如果查找一个元素,并不需要顺序查找(虽然这也是有可能的),可以直接跳过某些节点,因为总体是有序的,而且能够直接定位到内部的节点。

      一说到上面的那些性质,很自然的想到一些组织很规则的表,比如:而每隔两个节点都指向两个位置前的节点,每隔四个位置指向四个位置前的节点,如下图:

 

 

 

图二

       可以看出,跳转表的查找是很高效的。然而,跳转表的设计(像上面的规则的设计,图二)可能导致插入和删除过程极其低效。插入一个新节点时,所有在其后的节点都必须重新组织:引用域的个数和引用值都要改变。要保留跳转表再查找方面的某些优点,同时避免插入和删除的重组织问题,需要放弃不同层次节点的位置要求,没有结构规则的限制,也就没有重组织的问题了。像图一所示。

       我认为,跳转表的形式应该大致相同,只不过是实现的方式各异而已。我看书上的算法是这样的:确定一个最高层次maxLevel(这个最高层次对整个链表的结构影响很大),根据maxLevel生成一个数组powers,里面存放[1,9,13,15]四个数(如果maxLevel=5,则powers=[1,17,15,29,31]),是每一层的界限,它根据生成的随机数,确定应该把待插入的元素插入到第几层。插入时,生成一个1~15的随机数r,如果r在1~8之间,则插入到第一层,如果在9~12之间,插入到第二层,如果是13或14,插入到第三层,15则插入到第四层,然后根据插入到的层次数,生成一个节点,这个节点也有同样的引用域数量。

      具体程序如下:

import java.util.Random;

//节点类
class IntSkipListNode {
    public int key;
    public IntSkipListNode[] next;

    IntSkipListNode(int key, int num) {
        this.key = key;
        next = new IntSkipListNode[num];
        for (int i = 0; i < num; i++)
            next[i] = null;
    }
}

//链表类
public class IntSkipList {
    private int maxLevel;
    private IntSkipListNode[] root;
    private int[] powers;
    private Random rd = new Random();

    IntSkipList() {
        this(4);
    }

    IntSkipList(int num) {
        maxLevel = num;
        root = new IntSkipListNode[maxLevel];
        powers = new int[maxLevel];
        for (int i = 0; i < maxLevel; i++)
            root[i] = null;
        choosePowers();
    }

    public boolean isEmpty() {
        return root[0] == null;
    }

    //初始化数组power,设定每一层的数值范围的最低界限
    public void choosePowers() {
        powers[maxLevel - 1] = (2 << (maxLevel - 1)) - 1;
        for (int i = maxLevel - 2, j = 0; i >= 0; i--, j++)
            powers[i] = powers[i + 1] - (2 << j);
    }

   //生成一个随机数,根据这个随机数,来判断新插入的结点应该放到哪一层上。
    public int chooseLevel() {
        int ranInt = Math.abs(rd.nextInt());
        int i, r = ranInt % powers[maxLevel - 1] + 1;
        for (i = 1; i < maxLevel; i++)
            if (r < powers[i])
                return i - 1;
        return i - 1;
    }

    //找一个结点,找到了,返回这个数,没有找到,返回0
    public int skipListSearch(int key) {
        int lvl;
        IntSkipListNode prev, curr;
        for (lvl = maxLevel - 1; lvl >= 0 && root[lvl] == null; lvl--) //从上面扫描,直至不为null的指针区域
            ;
        prev = curr = root[lvl]; //指向最上面的那个指向不为null的结点
        while (true) {
            if (key == curr.key)
                return curr.key; //找到了,返回这个数
            else if (key < curr.key) { //找过了,往前找
                if (lvl == 0)
                    return 0; //达到末尾了,返回0,表示没有找到。
                else if (curr == root[lvl])
                    curr = root[--lvl];
                else
                    curr = prev.next[--lvl];
            } else { //还没到,接着往后找
                prev = curr;
                if (curr.next[lvl] != null) //后面还有
                    curr = curr.next[lvl];
                else { //后面没有了,就应该回到主表(IntSkipList),取到下一个指针所指对象
                    for (lvl--; lvl >= 0 && curr.next[lvl] == null; lvl--) //找到下面第一个不为null的结点
                        ;
                    if (lvl >= 0) //后面还有结点
                        curr = curr.next[lvl];
                    else //没有找到
                        return 0;
                }
            }
        }
    }

    //插入一个表中不存在的元素
    public void skipListInsert(int key) {
        IntSkipListNode[] curr = new IntSkipListNode[maxLevel];
        IntSkipListNode[] prev = new IntSkipListNode[maxLevel];
        IntSkipListNode newNode;
        int lvl;
        curr[maxLevel - 1] = root[maxLevel - 1];
        prev[maxLevel - 1] = null;

        //使prev指向最后一个小于key的结点,以便在它后面插入key结点;并使curr指向第一个大于
       //key的结点,以便使curr所指的结点加入到存储key的结点的next中

        for (lvl = maxLevel - 1; lvl >= 0; lvl--) {
            while (curr[lvl] != null && curr[lvl].key < key) {
                prev[lvl] = curr[lvl];
                curr[lvl] = curr[lvl].next[lvl];
            }
            if (curr[lvl] != null && curr[lvl].key == key)
                return;
            if (lvl > 0) {
                if (prev[lvl] == null) {
                    curr[lvl - 1] = root[lvl - 1];
                    prev[lvl - 1] = null;
                } else {
                    curr[lvl - 1] = prev[lvl].next[lvl - 1];
                    prev[lvl - 1] = prev[lvl];
                }
            }
        }
        lvl = chooseLevel(); //随机选择该结点所处的层数

        newNode = new IntSkipListNode(key, lvl + 1);
        for (int i = 0; i <= lvl; i++) {
            newNode.next[i] = curr[i];
            if (prev[i] == null)
                root[i] = newNode;
            else
                prev[i].next[i] = newNode;
        }
    }

    public static void main(String[] args) {//测试
        IntSkipList isl = new IntSkipList(4);
        isl.skipListInsert(5);
        isl.skipListInsert(17);
        isl.skipListInsert(35);
        isl.skipListInsert(7);
        isl.skipListInsert(8);
        isl.skipListInsert(12);
        isl.skipListInsert(19);
        isl.skipListInsert(10);
        isl.skipListInsert(22);
        isl.skipListInsert(31);
        isl.skipListInsert(28);
        isl.skipListInsert(33);
        if (!isl.isEmpty()) {
            System.out.println(isl.skipListSearch(20));
            System.out.println(isl.skipListSearch(33));
        }
    }
}

写的很乱,没办法,能力至此。这种结构好像不是很常见,但是我觉得很巧妙。

出自:《数据结构与算法——Java语言版(第二版)》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值