跳表的一些学习认识分享

跳表的背景(skipList)

在一般的有序链表中,如果要在n个数对中查询一个数对,至少要进行n次比较,如果我们在链表的中间结点加一个指针,则比较的次数会变成n/2+1;此时只需要先于中间的数对进行比较,如果大于此数对的的关键字,则只需要在链表的右半部分查找,反之,在链表的左半部分查找即可。

跳表的结构描述(stucture)

对n个数对来说,0级链表包含所有数对,1级链表每2个数对取一个,2级链表每4个数对取一个,i级链表每2i个数对取一个(相对于0级链表);
注:规则的跳表结构中,i-1级链表的数对数与i级链表的数对数之比为p;下图p=0.5
Alt

跳表的主要参数

  1. headNode(头节点指针)
  2. tailNode(尾结点指针)
  3. p (i-1级链表的数对数与i级链表的数对数之比为p 属于i-1级链表的数对同时属于i级链表的概率为p
  4. level(当前最大的非空链表级数)
  5. maxLevel(最大层数)

    maxLevel=log1/pN -1

  6. tailKey(最大关键字)

跳表的主要操作

级的分配

假设使用一个随机数生成器产生0到1之间的实数,产生的随机数<=p的概率为p,则这个数属于1级链表,下一个随机数如果也是<=p,那么则属于2级链表

if(rand()/RAND_MAX<=p)
	level++;

查找操作

查找操作是从最高级进行查找的,也就是level级进行查询,直到0级链表;
在每一级链表中,从左边尽可能逼近要查找的数对;
此时每一级没有必要去判断是否与theKey相等,原因是每一级链表的比较下来的平均时间是大于0级链表只进行一次的比较时间,且大多数的数对是在第0级的;

template<class K, class E>
pair<const K, E>* skipList<K, E>::find(const K& theKey) const
{// 返回匹配的数对的指针
 // 如果没有找到,返回NULL
    if (theKey >= tailKey)
        return NULL;  // 没有匹配的数对
     // beforNode 是距离theKey最近的结点
    skipNode<K, E>* beforeNode = headerNode;
    for (int i = levels; i >= 0; i--)          // 级数递减
       // 在每一级链表,从左边尽可能的逼近theKey
        while (beforeNode->next[i]->element.first < theKey)
            beforeNode = beforeNode->next[i];

    // 查看beforeNode的下一个节点是不是代查询的数对
    if (beforeNode->next[0]->element.first == theKey)
        return &beforeNode->next[0]->element;

    return NULL;  // 没有匹配
}

插入操作

在进行新数对的插入之前,首先是要进行新数对级的分配(theLevel);
在进入插入的时候,需要对 theLevel下每一级(包括theLevel) 的链表进行调整,所以需要知道每一级链表应该调整的位置;

template<class K, class E>
skipNode<K, E>* skipList<K, E>::search(const K& theKey) const
{// 查找每一级应该修改的位置
 // 返回关键字可能是theKey的结点
   // 同上查找操作,beforNode是最靠近theKey的结点
    skipNode<K, E>* beforeNode = headerNode;
    for (int i = levels; i >= 0; i--)
    {
        while (beforeNode->next[i]->element.first < theKey)
            beforeNode = beforeNode->next[i];
        last[i] = beforeNode;  //将每一个需要修改的位置记录
    }
    return beforeNode->next[0];
}

具体过程:在插入的时候,查找是否存在theKey关键字的结点,如果存在,覆盖即可;如果不存在,则需要进行级的分配,然后进行theLevel下每一级(包括theLevel) 的插入.
:如果分配的级数大于最大级数,则应该进行更新记录修改位置的数组, 即search代码中的last数组;

template<class K, class E>
void skipList<K, E>::insert(const pair<const K, E>& thePair)
{// 插入数对,覆盖已经存在的
    if (thePair.first >= tailKey) // 插入的数对关键字过大
    {
        ostringstream s;
        s << "Key = " << thePair.first << " Must be < " << tailKey;
        throw illegalParameterValue(s.str());
    }

    // 看字典中是否存在
    skipNode<K, E>* theNode = search(thePair.first);
    if (theNode->element.first == thePair.first)
    {//更新存在的数据
        theNode->element.second = thePair.second;
        return;
    }

    // 如果不存在,进行级数的分配
    int theLevel = level(); 
    // 确保theLevel<=levels+1
    if (theLevel > levels)
    {
        theLevel = ++levels;
        last[theLevel] = headerNode;
    }
    //插入数据,在thelevel下的(包括thelevel)每一级进行插入
    skipNode<K, E>* newNode = new skipNode<K, E>(thePair, theLevel + 1);
    for (int i = 0; i <= theLevel; i++)
    {// 在每一级进行插入
        newNode->next[i] = last[i]->next[i];
        last[i]->next[i] = newNode;
    }

    dSize++;
    return;
}

删除操作

在删除操作中,需要去search这个需要删除的数对,找到每一级左边与它最邻近的结点,然后进行每一级的删除.
删除完毕之后要进行级的更新(如果删除的结点恰好是最高级存在结点的链表)

template<class K, class E>
void skipList<K, E>::erase(const K& theKey)
{// 删除字典中theKey对应的数对
    if (theKey >= tailKey) // too large
        return;

    // 看最左边的结点的下一个节点是不是需要删除的结点
    //此时不直接用find()函数的原因是要进行每一级的调整
    skipNode<K, E>* theNode = search(theKey);
    if (theNode->element.first != theKey) // 不存在
        return;

    // 从跳表中删除
    for (int i = 0; i <= levels &&
        last[i]->next[i] == theNode; i++)
        last[i]->next[i] = theNode->next[i];

    // 更新级数
    while (levels > 0 && headerNode->next[levels] == tailNode)
        levels--;
    delete theNode;
    dSize--;
}

复杂度分析

  • 时间复杂性: find/insert/erase最坏的O(n+maxLevel) ,平均复杂度为O(logn)
  • 空间复杂性: 最坏的情况是,每一个数对都是maxLevel级,所以除过n个数对的空间,还要提供O(nMaxLevel)个指针空间 ;在一般情况下,第i级链有npi个数对,所以总共是 n ∑ ( p i ) = n ( 1 − p ) n\sum(p^i) = n(1-p) n(pi)=n(1p)个指针,在本例中,p=0.5,则只需要2n个指针空间.
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 PostgreSQL 数据库中,并没有内置的跳表(Skip List)索引实现。PostgreSQL 提供了多种索引类型,如B树索引、哈希索引、GiST索引和GIN索引等,但没有直接支持跳表的索引类型。 B树索引是 PostgreSQL 中最常用的索引类型之一。它适用于范围查询和等值查询,并且可以保持数据有序性。B树索引在处理数据块的平衡性和查询效率方面具有很好的性能。 除了B树索引之外,PostgreSQL 还提供了其他类型的索引用于特定的场景。例如,哈希索引适用于等值查询,可以提供快速的哈希查找;GiST 索引(通用搜索树)和 GIN 索引(通用倒排索引)适用于全文搜索和复杂的匹配查询。 虽然 PostgreSQL 不提供内置的跳表索引实现,但是你可以使用扩展或自定义索引实现跳表的功能。通过编写自定义插件或使用第三方扩展,你可以在 PostgreSQL 中实现跳表索引。这需要一定的开发工作,并且需要充分测试和评估性能。 需要注意的是,自定义实现的跳表索引可能会受到 PostgreSQL 内核版本更新的影响,并且可能无法享受到 PostgreSQL 内置索引的一些优化和支持。 总之,PostgreSQL 并没有内置的跳表索引实现,但提供了其他类型的索引,如B树索引、哈希索引、GiST索引和GIN索引等,用于满足不同的查询需求。如果需要使用跳表索引,你可以考虑自定义实现或使用第三方扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值