合并排序算法中的C分段错误
我试图在C语言中一个相当大的双链表的一个键上合并排序a,这个双链表大约有100000个元素。以下是DLL元素的结构:合并排序算法中的C分段错误,c,segmentation-fault,mergesort,C,Segmentation Fault,Mergesort,我试图在C语言中一个相当大的双链表的一个键上合并排序a,这个双链表大约有100000个元素。以下是DLL元素的结构: struct Pore { int ns; /* voxel number */ int radius; /* effective radius of porosity surrounding a pore */ struct Pore *next; struct Pore *prev; }; 在搜索了各种算法后,我发现最常用的算法包括三
struct Pore {
int ns; /* voxel number */
int radius; /* effective radius of porosity surrounding a pore */
struct Pore *next;
struct Pore *prev;
};
在搜索了各种算法后,我发现最常用的算法包括三个函数:mergeSort
、merge
和split
。我在这里包括他们。。。请原谅merge
函数中的多个printf
s,因为我一直在尝试调试在merge
函数的第4097592次递归输入时发生的分段错误。
Recur01
和Recur02
是我定义的全局变量,用于帮助调试
void mergeSort(struct Pore **head)
{
Recur01++;
/* Base case: 0 or 1 pore */
if ((*head) == NULL) {
printf("\nEnter mergeSort %ld, list head is NULL ",Recur01);
fflush(stdout);
return;
}
if ((*head)->next == NULL) {
printf("\nEnter mergeSort %ld, list head next is NULL ",Recur01);
fflush(stdout);
return;
}
printf("\nEnter mergeSort %ld",Recur01);
fflush(stdout);
/* Split head into 'a' and 'b' sublists */
struct Pore *a = *head;
struct Pore *b = NULL;
split(*head, &a, &b);
/* Recursively sort the sublists */
mergeSort(&a);
mergeSort(&b);
/* Merge the two sorted halves */
*head = merge(a,b);
printf("\nExit mergeSort %ld",Recur01);
fflush(stdout);
return;
}
void split(struct Pore *head, struct Pore **a, struct Pore **b)
{
int count = 0;
int lngth = 1;
struct Pore *slow = head;
struct Pore *fast = head->next;
struct Pore *temp;
temp = head;
while (temp->next != NULL) {
lngth++;
/*
printf("\n Length = %d",lngth);
fflush(stdout);
*/
if (temp->next) {
temp = temp->next;
}
}
while (fast != NULL) {
printf("\nCount = %d",count);
fflush(stdout);
fast = fast->next;
if (fast != NULL) {
slow = slow->next;
fast = fast->next;
}
count++;
}
printf("\nDone with while loop, final count = %d",count);
fflush(stdout);
*b = slow->next;
slow->next = NULL;
printf("\nExit split");
fflush(stdout);
return;
}
struct Pore *merge(struct Pore *a, struct Pore *b)
{
Recur02++;
if (Recur02 >= 4097591) {
printf("\nEnter merge %ld",Recur02);
fflush(stdout);
}
/** If first linked list is empty, return the second list */
/* Base cases */
if (a == NULL) return b;
if (b == NULL) return a;
if (Recur02 >= 4097591) {
printf("\n Made it 01");
fflush(stdout);
}
/* Pick the larger key */
if (a->radius > b->radius) {
if (Recur02 >= 4097591) {
printf("\n Made it 02 a is bigger, Recur02 = %ld",Recur02);
fflush(stdout);
printf(" a->next->ns = %d",a->next->ns);
fflush(stdout);
printf(" b->ns = %d",b->ns);
fflush(stdout);
}
a->next = merge(a->next,b);
a->next->prev = a;
a->prev = NULL;
if (Recur02 >= 4097591) {
printf("\nExit merge a %ld",Recur02);
fflush(stdout);
}
return a;
} else {
if (Recur02 >= 4097591) {
printf("\n Made it 02 b is bigger, Recur02 = %ld",Recur02);
fflush(stdout);
printf(" b->next->ns = %d",b->next->ns);
fflush(stdout);
printf(" a->ns = %d",a->ns);
fflush(stdout);
}
b->next = merge(a,b->next);
b->next->prev = b;
b->prev = NULL;
if (Recur02 >= 4097591) {
printf("\nExit merge b %ld",Recur02);
fflush(stdout);
}
return b;
}
}
正如我所说,运行代码是有效的,直到我进入merge
的第4097592个条目。我在函数调用前放置了一个printf
,在进入函数后立即放置了另一个。我还printf
函数参数中元素的键,它们看起来也不错。我不知道还有什么能把这件事弄清楚。以下是输出的最后几十行:
Exit mergeSort 529095
Exit mergeSort 529095
Enter merge 4097591
Made it 01
Made it 02 a is bigger, Recur02 = 4097591 a->next->ns = 156692 b->ns = 20
Enter merge 4097591
Enter merge 4097592
Made it 01
Made it 02 a is bigger, Recur02 = 4097592 a->next->ns = 156693 b->ns = 20
这是分段错误之前从缓冲区刷新的最后一行。关于如何调试这个问题,我已经没有什么想法了,所以如果有任何建议,我将不胜感激。@vladfrommosco建议使用非递归排序算法,因为递归排序算法不适合长列表。因此,我尝试为我的双链表调整一个迭代版本的合并排序。工作起来很有魅力。至少在这种情况下,对于这么长的列表来说,递归似乎真的太深了。分段错误是由于使用了递归合并,该合并为每个合并的节点调用自己。主代码自上而下是可以的,因为这将使堆栈空间复杂度为O(log2(n)),但是合并函数需要迭代 最常用 std::list::sort()的原始实现是一种自下而上的链表合并排序,它使用一个小数组(25到32个)列表(或指向列表第一个节点的指针或迭代器) 在VisualStudio2015之前,std::list::sort的大多数实现可能都是自下而上的,后者从使用列表数组切换到使用迭代器(以避免没有默认分配器之类的问题,并提供异常安全性)。这是在前面的一个线程中出现的,最初我只是接受了更改,假设切换到迭代器需要自顶向下的更改。这个问题后来又出现了,所以我研究了一下,确定没有必要切换到自顶向下的合并排序。我最遗憾的是没有从最初的问题开始研究这个问题。我确实更新了我的答案,以显示基于独立迭代器的自底向上合并排序,以及VS2019 include文件中std::list::sort的重放
在大多数情况下,只要有足够的内存,就可以更快地将列表复制到数组(或向量)、对数组排序以及创建新的排序列表。如果大型链表中的节点随机分散,则几乎每个被访问的节点都会发生缓存未命中。通过将列表移动到阵列,阵列中运行的按合并排序的顺序访问对缓存更加友好。这就是Java对链表的本机排序的实现方式,尽管部分原因是因为对多个容器类型(包括链表)使用了公共集合.sort(),虽然C++标准库STD::list是一个独立的容器类型,它具有列表特定的成员函数。建议#1:尝试用一个小数据集(比如5个条目,而不是4097592)重新生成segfault。建议#2:使用调试器,当segfault发生时,请求回溯并查看变量的值。您可以尝试增加程序的堆栈大小,查看崩溃是否仍然存在或移动到更大的索引。如果是这样,那么很可能由于列表的大小,您的递归太深了。