跳过列表C++分段错误

Skip List C++ segmentation fault

本文关键字:分段 错误 C++ 列表      更新时间:2023-10-16

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

法典:

#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。它似乎正在工作,但有时某些节点未正确插入,搜索结果不佳。

最后编辑: 它有效,感谢所有人

再编辑一次: 在代码中添加了注释,如果您有任何建议,欢迎您

代码中最大的问题可能是NIL=new SkipNode(numeric_limits<T*>::max());

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

然后,当执行insert方法中的第一个循环时,这会导致 2 个问题:

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

我首先建议你改变设计,让 SkipNode 保留一个值来T而不是指针:

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

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

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

因此,第一个问题是当您创建 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->forward[1];,但在循环 i = 0 之后,forward[0]在你的代码中没有任何意义。

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

cursor->~SkipNode();

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

在循环中,您可能想写update[i]->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。