【问题标题】:Skip List C++ segmentation fault跳过列表 C++ 分段错误
【发布时间】:2018-02-18 23:32:23
【问题描述】:

我正在尝试使用这篇文章Skip List 来实现跳过列表。

代码:

#include<iostream>
#include<cstdlib>
#include<ctime>
#include<limits>

using namespace std;

template<class T>
class SkipList{
    private:
        class SkipNode{
            public:
                T* key; //Pointer to the key
                SkipNode** forward; //Forward nodes array
                int level; //Node level
                //SkipNode constructor
                SkipNode(T* key, int maxlvl, int lvl){
                    forward = new SkipNode*[maxlvl];
                    this->key=key;
                    level=lvl;
                }
                //Method that print key and level node
                print(){
                    cout << "(" << *key << "," << level << ") ";
                }
        };

        SkipNode *header,*NIL; //Root and End pointers
        float probability; //Level rate
        int level; //Current list level
        int MaxLevel; //Maximum list levels number

        //Function that returns a random level between 0 and MaxLevel-1
        int randomLevel(){
            int lvl = 0;
            while( (float(rand())/RAND_MAX < probability) && (lvl < MaxLevel-1) )
                lvl++;
            return lvl;
        }

    public:
        //SkipList constructor
        SkipList(float probability, int maxlvl){
            this->probability = probability;
            MaxLevel = maxlvl;
            srand(time(0));
            header=new SkipNode(NULL,MaxLevel,0); //Header initialization
            T* maxValue = new T;
            *maxValue = numeric_limits<T>::max(); //Assign max value that T can reach
            NIL = new SkipNode(maxValue,0,0); //NIL initialization  
            level=0; //First level
            for(int i=0; i<MaxLevel; i++){ //Every header forward node points to NIL
                header->forward[i]=NIL;
            }
        }

        //SkipList destructor
        ~SkipList(){
            delete header;
            delete NIL;
        }

        //Method that search for a key in the list
        SkipNode* search(T* key){
            SkipNode* cursor = header;
            //Scan the list
            for(int i=level; i>=0; i--)
                while(*(cursor->forward[i]->key) < (*key))
                    cursor=cursor->forward[i];  
            cursor=cursor->forward[0];
            if(*(cursor->key) == *key) 
                return cursor;
            return NULL;
        }

        //Method that insert a key in the list
        SkipList* insert(T* key){
            SkipNode* cursor = header;
            SkipNode* update[MaxLevel]; //Support array used for fixing pointers
            //Scan the list
            for(int i=level; i>=0; i--){
                while(*(cursor->forward[i]->key) < *(key))
                    cursor=cursor->forward[i];
                update[i]=cursor;
            }
            cursor=cursor->forward[0];
            if(*(cursor->key) == *(key)){ //Node already inserted
                return this;
            }
            int lvl = randomLevel(); //New node random level
            if(lvl > level){ //Adding missing levels
                for(int i=level+1; i<=lvl; i++)
                    update[i]=header;
                level=lvl;
            }
            SkipNode* x = new SkipNode(key,MaxLevel,lvl); //New node creation
            for(int i=0; i<=lvl; i++){ //Fixing pointers
                x->forward[i] = update[i]->forward[i];
                update[i]->forward[i] = x;
            }
            return this;
        }

        //Method that delete a key in the list
        SkipList* erase(T* key){
            SkipNode* cursor = header;
            SkipNode* update[MaxLevel]; //Support array used for fixing pointers
            //Scan the list
            for(int i=level; i>=0; i--){
                while(*(cursor->forward[i]->key) < *(key))
                    cursor=cursor->forward[i];
                update[i]=cursor;
            }
            cursor=cursor->forward[0];
            if(*(cursor->key) == *(key)){ //Deletetion of the founded key
                for(int i=0; i<=level && update[i]->forward[i] == cursor; i++){
                    update[i]->forward[i] = cursor->forward[i];
                }
                delete cursor;
                while(level>0 && header->forward[level]==NIL){
                    level=level-1;  
                }
            }
            return this;
        }

        //Method that print every key with his level
        SkipList* print(){
            SkipNode* cursor = header->forward[0];
            int i=1;
            while (cursor != NIL) {
            cursor->print();
            cursor = cursor->forward[0];
            if(i%15==0) cout << endl; i++;
            }
            cout << endl;
            return this;
        }
};

main(){
    SkipList<int>* list = new SkipList<int>(0.80, 8);
    int v[100];
    for(int i=0; i<100; i++){
        v[i]=rand()%100;
        list->insert(&v[i]);
    }
    list->print();
    cout << endl << "Deleting ";
    for(int i=0; i<10; i++){
        int h = rand()%100;
        cout << v[h] << " ";
        list->erase(&v[h]);
    }
    cout << endl;
    list->print();
    cout << endl;
    for(int i=0; i<10; i++){
        int h = rand()%100;
        cout << v[h] << " ";
        if(list->search(&v[h]))
            cout << " is in the list" << endl;
        else
            cout << " isn't in the list" << endl; 

    }
    delete list;
}

它在第 59 行(插入的 for 循环)上给了我分段错误,但我不明白为什么。你能帮帮我吗?我会接受您建议的任何其他改进。我的截止日期是两天,这就是我寻求帮助的原因。

编辑: 我已经用 bebidek 建议更正了代码(谢谢)。现在第一级为 0。它似乎可以正常工作,但有时某些节点未正确插入,搜索结果不佳。

最后编辑: 可以了,谢谢大家

更多编辑: 在代码中添加了 cmets,如果您有任何建议,欢迎您

【问题讨论】:

  • 你调试了吗?
  • 请解释一下,这是什么:NIL=new SkipNode(numeric_limits&lt;T*&gt;::max()); 应该做什么?您创建了一个指针数组,指向某个无效值,然后,在插入时,您尝试取消引用该值,并惊讶于它向您咆哮(以段错误的形式),因为它?
  • 是的,我尝试调试它,但我无法理解我的错误。我已经更正了 NIL 节点,它包含 T 可以假设的最大值。
  • 你可以写出你最后修改的内容,因为我在你的新代码中发现了三个问题,但其中只有一个会给你错误的答案。
  • 我在插入中的 for 中添加了“+1”,并在搜索中取出“+1”。现在它似乎工作了。我只需要修复析构函数

标签: c++ skip-lists


【解决方案1】:

你代码中最大的问题可能是NIL=new SkipNode(numeric_limits&lt;T*&gt;::max());

首先,我怀疑您希望key 指针指向包含最大可能int 值的内存地址。 但这不是这里实际发生的事情。相反,key 指针指向可能对您的进程不可用的最大内存地址。 此外,forward 属性可能包含一个垃圾指针数组。

那么当insert方法中的第一个循环被执行时,这会导致两个问题:

  1. while(*(cursor-&gt;forward[i]-&gt;key) &lt; *(key)) 会将键值与无效指针进行比较
  2. cursor=cursor-&gt;forward[i]; 会将光标重新分配给无效指针

我首先建议您更改设计,让 SkipNode 将值保留为 T 而不是指针:

class SkipNode{
public:
  T key;
  SkipNode* forward[100];

这将使大量与指针相关的代码变得不必要,并使代码更简单,因此不太可能遇到访问冲突。

此外,使用实际的 NULL(或事件更好的 nullptr)值而不是虚拟的 NIL 值来指示列表的结尾可能更简洁。

【讨论】:

  • 感谢您的帮助。我更喜欢使用指针,所以这个结构可以与继承的类一起使用,我只将整数用于测试。
【解决方案2】:

所以,第一个问题是当你创建 NIL 节点时:

NIL=new SkipNode(numeric_limits<T*>::max());

您应该使用指向现有变量的指针作为参数,例如:

T* some_name = new T;
*some_name = numeric_limits<T>::max();
NIL = new SkipNode(some_name);

注意,我在numeric_limits 中使用了T 而不是T*。当然你必须记住在析构函数中删除这个变量。

第二个问题是代码中的 level 变量有时是包含性的(我的意思是级别号 level 存在),如第 61 行,有时排他性(级别号 level 不存在)如第 71 行. 你必须保持一致。

第三个问题在第 52 行。你的意思可能是 cursor=cursor-&gt;forward[1];,但在循环 i = 0 之后,forward[0] 在你的代码中没有任何意义。

编辑: 第四个和第五个问题是擦除功能。

cursor->~SkipNode();

它不会删除您的节点,而只会运行空析构函数。请改用delete cursor;

在循环中你可能想写update[i]-&gt;forward[i] == cursor而不是!=

更多编辑: 您还没有实现 SkipList 的任何析构函数,并且您在 main() 的末尾忘记了 delete list;。这两个会给你一个内存泄漏。

另一个编辑:

srand(time(0));

这一行应该在 main 的开头执行一次,仅此而已。如果您在每次随机生成之前执行它,每次都会得到相同的结果(因为 time(0) 只计算秒数,并且您的程序可以每秒多次运行函数 randomLevel())。

你也忘了在SkipList的构造函数中重写precision变量。

下一个编辑: 在您的 insert 函数中,您没有级别随机化。我的意思是,您没有能力插入低于整个跳过列表级别的节点。导致程序崩溃或给出错误结果的不是错误,而是结构中查询的时间复杂度是 O(n) 而不是 O(log n)。

您应该在插入函数的这个循环中使用lvl 而不是level

for(int i=1; i<level; i++){
    x->forward[i] = update[i]->forward[i];
    update[i]->forward[i] = x;
}

而且你的随机函数randomLevel 的最小结果应该是 1 而不是 0,因为你不希望节点女巫级别 = 0。

【讨论】:

  • 谢谢你,你帮了我很多。对于菜鸟错误,我很抱歉,但我发烧了,所以我感觉不舒服。我已经编辑了帖子,我犯了最后一个错误,请你再帮我一次吗?
猜你喜欢
  • 1970-01-01
  • 2016-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多