【问题标题】:QUICK SORT: Printing Memory Addresses instead of array elements:快速排序:打印内存地址而不是数组元素:
【发布时间】:2020-01-09 15:30:16
【问题描述】:

我已经编写了对整数数组进行快速排序的代码,但是在打印时它会在终端上显示某种内存地址。想了很久,还是没有成功。

我尝试在在线 gdb 编译器上编译它,它给出了一个错误:Segmentation Fault(core dumped),否则我尝试修改代码几次

#include<stdio.h>
void swap(int* a, int* b){
    int t = *a;
    *a = *b;
    *b = t;
}
int part(int arr[], int l, int r){    //for partioning the array
    int i=l, j=0;

    while(j<r){
            if(arr[j]<arr[r]){
                swap(&arr[j], &arr[i]);
                i++;
            }
        j++;
    }
    swap(&arr[i], &arr[r]);
return i;
}
void qsort(int arr[], int l, int r){    //recursive function for quicksort
    if(l<r){
        int p = part(arr, l, r);
        qsort(arr, l, p-1);
        qsort(arr, p+1, r);
    }
}
int main(void){
    int arr[] = {4, 10, 17, 5, 5, 6, 1 , 3, 0};

    qsort(arr, 0, 8);                 //function called

    for(int i=0; i<9; i++){            
        printf("%d ", arr[i]);       //for printing the array
    }
return 0;
}

实际结果:
11016472 1974520423 11016472 6356864 6 6356800 4214784 11015616 1974521024

我期待数组被排序。

【问题讨论】:

  • qsort 是标准库函数的名称,自己定义的行为不是C标准定义的。重命名并重试。
  • 当我在我的机器上运行它时,它会输出10 17 6 5 3 0 4 5 1。没有完全排序,但它仍然是数组的原始值。
  • @Eric Posypischil 试过了,还是不行。
  • @scatter 是的,有几次我也得到了原始值。
  • @thebusybee: C 2018 7.1.3 表示标准库的标识符是保留的,而 7.1.4 表示将它们定义为明确允许之外的行为具有标准未定义的行为:“如果程序在保留标识符的上下文中声明或定义标识符(7.1.4 允许的除外),或将保留标识符定义为宏名称,则行为未定义。”

标签: c algorithm sorting segmentation-fault quicksort


【解决方案1】:

您的分区已损坏。它似乎松散地基于两种算法,您都没有正确实现。

int part(int arr[], int l, int r){    //for partitioning the array

这告诉我们我们有一些数组,我们想要划分lr 之间的范围。目前尚不清楚该目标是否包含这些索引,但这并不重要,因为后续代码无论如何都会被破坏。

int i=l, j=0; // HERE. Why is j 0 ??

稍后,您可以多次访问arr[j],包括读取和写入。如果您要分区的范围在 [l..r] 中,则无法保证从零开始的 j 在该范围内。从l 开始j 是合适的。

但是有更好的方法。

首先,这是 C。在 part 中,id arr 不是数组;这是一个指针。假设我们有一个序列,其中l 总是从零开始,r 只是长度。在这种情况下,l 参数将毫无意义,而我们的part 函数可以这样开始:

int part(int arr[], int len)

很公平。现在我们可以实现一个简单的分区,它选择一个枢轴,基于该枢轴进行分区,并返回枢轴结束位置的索引。我不会讨论选择“好”支点的相关主题。有几种方法,我鼓励您自己进行调查。为了简单起见,我们将在此示例中使用最右边的元素枢轴:

int part(int arr[], int len)
{
    if (len <= 1)
        return 0;

    int pvt = 0;
    for (int i=0; i<len; ++i)
    {
        if (arr[i] < arr[len-1]) 
            swap(arr+i, arr + pvt++);
    }
    swap(arr+pvt, arr+len-1);

    return pvt;
}

足够简单。现在这对实际的快速排序有何帮助?它有助于在对序列进行分区时使用偏移地址作为前导参数。这也很有帮助,因为现在我们可以对 q_sort 做与对 part 所做的相同的事情,即去掉一个参数,只使用基地址和长度:

void q_sort(int arr[], int len)
{
    if (len <= 1)
        return;

    int p = part(arr, len);
    q_sort(arr, p);
    q_sort(arr+p+1, len-p-1);
}

就是这样。请注意,第二个递归调用使用指针算法将目标序列的基地址定位在枢轴位置之后的位置。长度相应调整。

完整的程序如下所示,包括一个生成 30 个元素序列的测试工具,然后对结果进行排序和显示:

代码

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

void swap(int* a, int* b)
{
    int t = *a;
    *a = *b;
    *b = t;
}

int part(int arr[], int len)
{
    if (len <= 1)
        return 0;

    int pvt = 0;
    for (int i=0; i<len; ++i)
    {
        if (arr[i] < arr[len-1])
            swap(arr+i, arr + pvt++);
    }
    swap(arr+pvt, arr+len-1);

    return pvt;
}

void q_sort(int arr[], int len)
{
    if (len <= 1)
        return;

    int p = part(arr, len);
    q_sort(arr, p);
    q_sort(arr+p+1, len-p-1);
}

#define N_ELEMS     30

int main(void)
{
    int arr[N_ELEMS];

    srand((unsigned int)time(NULL));
    for (int i=0; i<N_ELEMS; ++i)
    {
        arr[i] = 1 + (rand() % 99);
        printf("%d ", arr[i]);
    }
    fputc('\n', stdout);


    q_sort(arr, N_ELEMS);

    for (int i=0; i<N_ELEMS; ++i)
    {
        printf("%d ", arr[i]);
    }
    fputc('\n', stdout);

    return 0;
}

输出(显然不同)

21 95 18 10 57 60 60 6 37 72 44 99 91 25 14 5 50 58 16 18 81 5 52 42 15 40 58 16 76 61 
5 5 6 10 14 15 16 16 18 18 21 25 37 40 42 44 50 52 57 58 58 60 60 61 72 76 81 91 95 99 

这里有两个好处。首先,初始调用者不需要提供序列的两端。其次,函数实际上更简单

希望对你有帮助。

【讨论】:

  • 这不是严格意义上的快速排序,是吗?它不遵循正常的(jl 的 OP 代码)约定。 (这是一个合理的问题,而不是修辞)
  • 它是快速排序。分区机制通常是两种形式之一。这是Lomuto partition scheme,在教授分治法和快速排序的整体技术时经常使用它,因为它太容易理解了。
【解决方案2】:

您需要在输入part 时将j 设置为l,而不是0

...
int part(int arr[], int l, int r){    //for partioning the array
    int i=l, j=l;

    while(j<r){ // line 10
...

如果你不这样做,当r 大于8 时,你最终会读取(并且可能写入)数组的边界。

【讨论】:

    【解决方案3】:

    在某种程度上,当l 大于 0 时,从 part 中的 0 开始的 j 包括正在处理中的 l“左侧”的部分数组,并可能导致 i(其中被初始化为 l) 被递增了太多次,超过了 r

    int i=l, j=0; 更改为int i=l, j=l; 会导致程序为问题中的案例打印所需的输出。

    这个问题可以通过修改qsortpart在每次调用它们时打印它们的参数,或者在它们返回时打印或者将之前的打印缩进当前递归深度(可以是出于调试目的,使用静态计数器进行跟踪)。这会立即显示对qsort 的调用,其中r 设置为9,超出数组,然后导致检查它是如何变成这样的。

    【讨论】:

      【解决方案4】:

      第一次调用 part() 时,'i' 永远不会增加,因为没有任何值小于 arr[8]。该函数将返回 'i',即 0。然后调用 qsort(arr, l,p - 1 (即 -1))。此时您将使用 arr[-1] 进行交换。由于不正确的索引,arr 数组的大部分值将与堆栈中的随机数据交换。这就是为什么你会得到奇怪的值。

      int part(int arr[], int l, int r){    //for partioning the array
      int i=l, j=0;
      
      while(j<r){
              if(arr[j]<arr[r]){
                  swap(&arr[j], &arr[i]);
                  i++;
              }
          j++;
      }
      swap(&arr[i], &arr[r]);
      return i;
      }
      

      【讨论】:

      • 您是否更改了代码?看起来你刚刚重新发布了 op 的错误代码。
      • qsort 的主体包裹在if(l&lt;r) 中。如果用qsort(arr, l, p-1); 调用它,l 等于 0 和 p 等于 -1,那么它接收到 0 表示 l 和 -2 表示 r,并且 l&lt;r 为假,所以它确实什么都没有。
      猜你喜欢
      • 2016-07-04
      • 2014-07-26
      • 2011-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-23
      相关资源
      最近更新 更多