【问题标题】:Quicksort of Linked List in CC中链表的快速排序
【发布时间】:2014-03-05 02:56:05
【问题描述】:

好的,伙计们,在 gdb 和 valgrind 没有成功之后,我谦虚地向你们提出我的问题。我们被要求在 c 中实现一个快速排序的版本,使用列表的第一个元素作为枢轴(以保持与我们在学期早些时候所做的简单 haskell 实现的可比性)。提供了 LIST 实现(制作、打印和结构定义),其余的由我们决定。我(惊讶,惊讶)收到一个段错误,但 valgrind 出现了大量错误以及堆栈溢出,所以也许一些新的眼睛可以帮助我。

我的代码:

    #include <stdio.h>
#include <stdlib.h>

typedef struct CELL *LIST;
struct CELL {
    int element;
    LIST next;
};


LIST MakeList();
void PrintList(LIST);
LIST qSort(LIST);
int listLength(LIST);
LIST combine(LIST, LIST, LIST);


int main(){
   LIST list;
    printf ("enter a several numbers separated by spaces or returns and ended by Ctrl-D\n");
    list = MakeList();
    PrintList(qSort(list));
   return 0;
}

LIST qSort(LIST list){
   LIST current, pivot = list, temp = NULL;//use first element as pivot, start comparison at list->next
   LIST little, big, littleHead, bigHead;
   little = (LIST) malloc(sizeof(struct CELL));
   little->element = 0;
   little->next = NULL;
   littleHead = little;
   big = (LIST) malloc(sizeof(struct CELL));
   big->element = 0;
   big->next = NULL;
   bigHead = big;

   if(listLength(list) <= 1){//base case
      return list;
   }
//remove pivot by setting current to list->next
   current = list->next;

   do{
      if(current->element <= pivot->element){
         little->element = current->element;
         little->next = (LIST) malloc(sizeof(struct CELL));
         little = little->next;
         little->next = NULL;
      }
      else{
         big->element = current->element;
         big->next = (LIST) malloc(sizeof(struct CELL));
         big = big->next;
         big->next = NULL;
      }

      current = current->next;
   }while(current != NULL);

   littleHead = qSort(littleHead);
   bigHead = qSort(bigHead);

   return combine(littleHead, bigHead, pivot);
}

int listLength(LIST list){
   int length = 0;
   LIST current = list;
   if(NULL==list){
      return length;
   }
   else{
      while(current != NULL){
         current = current->next;
         length++;
      }
   }
   return length;
}



LIST combine(LIST little, LIST big, LIST pivot){
   LIST temp = little;
   while(temp->next != NULL){
      temp = temp->next;
   }
   temp->next = pivot;
   pivot->next = big;
   return little;
}

LIST MakeList()
{
    int x;
    LIST pNewCell;

    if (scanf("\%d", &x) == EOF) return NULL;
    else {
        pNewCell = (LIST) malloc(sizeof(struct CELL));
        pNewCell->next = MakeList();
        pNewCell->element = x;
        return pNewCell;
    }
}

void PrintList(LIST list)
{
    while (list != NULL) {
        printf("\%d\n", list->element);
        list = list->next;
    }
}

还有 valgrind 的输出

==20391== Conditional jump or move depends on uninitialised value(s)
==20391==    at 0x804855D: qSort (2100assignment4.c:45)
==20391==    by 0x80485E0: qSort (2100assignment4.c:61)
==20391==    by 0x80484BD: main (2100assignment4.c:22)
==20391==  Uninitialised value was created by a heap allocation
==20391==    at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==20391==    by 0x8048574: qSort (2100assignment4.c:47)
==20391==    by 0x80484BD: main (2100assignment4.c:22)
==20391==
==20391== Conditional jump or move depends on uninitialised value(s)
==20391==    at 0x804855D: qSort (2100assignment4.c:45)
==20391==    by 0x80485E0: qSort (2100assignment4.c:61)
==20391==    by 0x80485E0: qSort (2100assignment4.c:61)
==20391==    by 0x80484BD: main (2100assignment4.c:22)
==20391==  Uninitialised value was created by a heap allocation
==20391==    at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==20391==    by 0x8048574: qSort (2100assignment4.c:47)
==20391==    by 0x80484BD: main (2100assignment4.c:22)
==20391==
==20391== Conditional jump or move depends on uninitialised value(s)
==20391==    at 0x804855D: qSort (2100assignment4.c:45)
==20391==    by 0x80485E0: qSort (2100assignment4.c:61)
==20391==    by 0x80485E0: qSort (2100assignment4.c:61)
==20391==    by 0x80485E0: qSort (2100assignment4.c:61)
==20391==    by 0x80484BD: main (2100assignment4.c:22)
==20391==  Uninitialised value was created by a heap allocation
==20391==    at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==20391==    by 0x8048574: qSort (2100assignment4.c:47)
==20391==    by 0x80485E0: qSort (2100assignment4.c:61)
==20391==    by 0x80484BD: main (2100assignment4.c:22)
==20391==
==20391== Stack overflow in thread 1: can't grow stack to 0xbe297ff4
==20391==
==20391== Process terminating with default action of signal 11 (SIGSEGV)
==20391==  Access not within mapped region at address 0xBE297FF4
==20391==    at 0x402BE35: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==20391==  If you believe this happened as a result of a stack
==20391==  overflow in your program's main thread (unlikely but
==20391==  possible), you can try to increase the size of the
==20391==  main thread stack using the --main-stacksize= flag.
==20391==  The main thread stack size used in this run was 8388608.
==20391== Stack overflow in thread 1: can't grow stack to 0xbe297fe4
==20391==
==20391== Process terminating with default action of signal 11 (SIGSEGV)
==20391==  Access not within mapped region at address 0xBE297FE4
==20391==    at 0x4025430: _vgnU_freeres (in /usr/lib/valgrind/vgpreload_core-x86-linux.so)
==20391==  If you believe this happened as a result of a stack
==20391==  overflow in your program's main thread (unlikely but
==20391==  possible), you can try to increase the size of the
==20391==  main thread stack using the --main-stacksize= flag.
==20391==  The main thread stack size used in this run was 8388608.
==20391==
==20391== HEAP SUMMARY:
==20391==     in use at exit: 4,190,952 bytes in 523,869 blocks
==20391==   total heap usage: 523,869 allocs, 0 frees, 4,190,952 bytes allocated
==20391==
==20391== LEAK SUMMARY:
==20391==    definitely lost: 0 bytes in 0 blocks
==20391==    indirectly lost: 0 bytes in 0 blocks
==20391==      possibly lost: 0 bytes in 0 blocks
==20391==    still reachable: 4,190,952 bytes in 523,869 blocks
==20391==         suppressed: 0 bytes in 0 blocks
==20391== Rerun with --leak-check=full to see details of leaked memory
==20391==
==20391== For counts of detected and suppressed errors, rerun with: -v
==20391== ERROR SUMMARY: 261929 errors from 3 contexts (suppressed: 0 from 0)

【问题讨论】:

    标签: c segmentation-fault stack-overflow quicksort


    【解决方案1】:

    开始的一些问题:

    if (scanf("\%d", &x) == EOF) return NULL;
    

    不需要\。检查 != 1 可能更容易,因为您期望一个值。

    这导致您的 MakeList 无限递归(至少在我的机器上)。

    在您的 qsort 函数中,您的小列表和大列表总是在末尾有一个虚拟条目 - 由此代码完成

    little->next = (LIST) malloc(sizeof(struct CELL));
    little = little->next;
    little->next = NULL;
    

    这意味着,当您拆分列表时,您总是会得到比开始时更多的条目,因此它永远不会结束。另请注意,这些新条目没有设置其元素值,这可能是您收到未初始化警告的地方。

    您应该重新考虑如何存储子列表,也许将它们以 NULL 开头,以表示为空,尽管这会使添加新值有点困难。

    【讨论】:

    • 那个scanf实现实际上是由教授提供的。它期望 Ctrl-D 结束列表,(据我了解)在我们的 unix 机器上模拟 EOF。这有点胡闹,但我想他有点懒惰。不过感谢您的建议,我会弄乱我的子列表存储并报告。
    【解决方案2】:

    您可以如下编辑您的清单功能..

    您会观察到一些变化。

    1. scanf 通过删除 \ 是正确的(参考 The Dark 的回答)
    2. scanf while read in,这将确保如果您输入任何不兼容的输入(例如任何字母而不是 Ctrl + D),读取将停止。
    LIST MakeList()
    {
        int x;
        LIST pNewCell;
    
        while(scanf("%d", &x))
        {
            pNewCell = (LIST) malloc(sizeof(struct CELL));
            pNewCell->next = MakeList();
            pNewCell->element = x;
            return pNewCell;
        }
        return NULL;
    }
    

    【讨论】: