无法从第三版算法介绍中获得插入排序。正确的。我的思维错误在哪里?

Posted

技术标签:

【中文标题】无法从第三版算法介绍中获得插入排序。正确的。我的思维错误在哪里?【英文标题】:Can't get insertion sort from introduction to algorithms 3rd ed. right. Where is my thinking mistake? 【发布时间】:2011-10-10 23:10:42 【问题描述】:

我正在阅读《算法导论》,第 3 版这本书。首先要解释的事情之一是插入排序。在第 18 页有一些伪代码:

A = 5, 2, 4, 6, 1, 3 ;

INSERTION-SORT(A)
1 for j = 2 to A.length
2   key = A[j]
4   i = j - 1

5   while (i > 0 and A[i] > key)
6     A[i + 1] = A[i]
7     i = i - 1

8   A[i + 1] = key

它说使用了伪代码,因此可以轻松地将其翻译成任何类型的语言(C、C++、Java,他们没有提到,但我猜 C# 也是)。由于我是用 C# 编程的,所以我用 LinqPad 翻译了它。

int[] a =  5, 2, 4, 6, 1, 3 ;

for (var j = 1; j < a.Length; j++)

    var key = a[j];

    var i = j - 1;

    while(i > 0 && a[i] > key)
    
        a[i + 1] = a[i];
        i--;
    

    a[i + 1] = key;


a.Dump();

你可能会问,为什么 j 从 1 开始,而它却明明是 2?在书中,数组的索引从 1 开始。是的,我现在可能应该更新所有 [i - 1][i + i]

无论如何,完成后,我运行代码并注意到它实际上并没有正确排序。输出为 5, 1, 2, 3, 4, 6 。已经很晚了,应该停止,但我努力使代码正确。我做了所有事情,甚至按照书中的伪代码(从 2 开始)。仍然不是正确的输出。

我联系了本书的一位教授,他将插入排序的代码发给我,用 C 语言编写:

void insertion_sort(int *A, int n) 
  for (int j = 2; j <= n; j++) 
    int key = A[j];
    int i = j-1;

    while (i > 0 && A[i] > key) 
      A[i+1] = A[i];
      i--;
    

    A[i+1] = key;
  

用C#翻译:

int[] a = 5, 2, 4, 6, 1, 3 ;

for (var j = 2; j <= a.Length; j++)

    var key = a[j];

    var i = j - 1;

    while(i > 0 && a[i] > key)
    
        a[i + 1] = a[i];
        i--;
    

    a[i + 1] = key;

我得到一个超出范围的数组。好吧,那么也许:

int[] a = 5, 2, 4, 6, 1, 3 ;

for (var j = 2; j <= a.Length - 1; j++)

    var key = a[j];

    var i = j - 1;

    while(i > 0 && a[i] > key)
    
        a[i + 1] = a[i];
        i--;
    

    a[i + 1] = key;

输出: 5, 1, 2, 3, 4, 6

我在想,这不可能是正确的。伪代码对 array.Length 说 2。是 2

我个人认为是因为while循环中的0 &gt; 0谓词。实际上每次都差一点。

我的解释(来自我发给教授的电子邮件,懒得把它全部输入):

循环仍然以 5, 1, 2, 3, 4, 6 结束的原因是i &gt; 0 谓词。每次在 while 循环中减去 i 的 1 (i--)。这最终将导致0 &gt; 0 最终为假(只有0 == 0 将返回真),但此时循环仍需要再运行一次。它连续下降一分。它应该再执行 1 次 while 循环才能正确排序。

另一种解释:

当 j 以 2 开头时,key == 4,i == 1 和 a[i] == 2。在这种情况下,while 循环不会运行,因为 2 > 0 但 2 不大于 4。

j == 3, key == 6, i == 2, a[i] == 4

while 循环不会运行,因为 4 不大于 6

j == 4, key == 1, i == 3, a[i] == 6

这次循环运行时:

a[i + 1] = a[i] -> a[4] = a[3] -> 5, 2, 4, 6, 6, 3 i-- -> i == 2

再次循环,因为 2 > 0 和 4 > 1

a[i + 1] = a[i] -> a[3] = a[2] -> 5, 2, 4, 4, 6, 3 i-- -> i == 1

再次循环,因为 1 > 0 和 2 > 1

a[i + 1] = a[i] -> a[2] = a[1] -> 5, 2, 2, 4, 6, 3 i-- -> i == 0

这是错误的地方(在我看来)。 i 现在等于 0,但 while 循环应该再运行一次以使 5 离开第零位。

教授向我保证他是正确的,但我无法得到正确的输出。我的思路哪里错了?


教授发给我的 C 代码中的数组实际上是从索引 1 开始的。我不知道这一点,检查 C 数组时我发现它们都以 0 开头。是的,然后 C代码不会产生正确的输出。教授向我解释了这一点,现在这些碎片都落到了它的位置。

【问题讨论】:

我知道的每种编程语言都从 0 开始索引数组。我认为 MATLAB 和 R 可能是例外,但它们不是真正的编程语言。 :-) 【参考方案1】:

我认为教授使用的是基于 1 的数组表示法,因此使用 while (i &gt; 0 &amp;&amp; a[i] &gt; key),您在循环中缺少 a[0] 元素。将您的初始代码更改为此然后它可以工作:

for (var j = 1; j < a.Length; j++)

    var key = a[j];

    var i = j - 1;

    while(i >= 0 && a[i] > key)  <----------- Try this, or you'd miss the first number
    
        a[i + 1] = a[i];
        i--;
    

    a[i + 1] = key;

另外,如果你想使用教授的代码,请忽略那里的第 0 个元素。

顺便说一句,您联系了谁?铆钉?科尔曼?下次我弄糊涂的时候我想我也会尝试联系他,因为看起来这个教授回复邮件:)

【讨论】:

是的,i &gt;= 0 确实有效。尽管与您的解决方案有些不同,但我确实找到了如何使排序起作用的方法-这是您在其他教科书中经常看到的一种。不用i &gt;= 0,您将创建while 循环的第二个谓词a[i - 1],并在while 循环中使用第一行而不是a[i + 1] = a[i],如a[i] = a[i - 1]。我联系的教授是科曼。虽然他回复得很好,但他似乎很生气,因为我认为代码中可能存在错误。 “我认为教授正在使用基于 1 的数组表示法” - 我收到了一封电子邮件,这确实是发生的事情。我不知道为什么我得到的代码的数组的索引以 1 开头。我查找了 C 数组并认为它们总是以 0 开头。 @Garth:好吧,我猜 Corman 只是懒惰:) 毕竟,由于他使用指针,他可以将输入视为从 1 开始,因为在他的代码中 A[ 0] 从不使用。【参考方案2】:

你不应该考虑翻译伪代码,而应该考虑 翻译你对算法的理解。

数组起初是完全未排序的。该算法通过 获取连续的未排序元素并将它们插入到 已经排序的部分。开始的“排序部分”是第一个元素, 这是微不足道的“排序”。因此,要插入的第一个元素是 第二。第二个元素的索引是哪个?你的j 必须 从那里开始。

然后,i 必须遍历每个已排序元素的索引, 向后,直到找到插入当前值的位置 或用完元素。那么,它必须从哪里开始,从哪里开始 它必须结束吗?注意它实际上查看每个元素 是必须的。

一个错误是出了名的难以发现(和混合 基于 1 和基于 0 的数组的概念肯定没有帮助),但不要 只是摆弄,直到它似乎工作。试着理解什么 代码实际上是在做的。

【讨论】:

我完全同意——这就是我所做的。我把它拆开,看了看运动部件,我明白了。我知道它是如何工作的,我可以让它工作。回溯到伪代码和我从教授那里得到的代码我很困惑,因为我根本无法获得正确的输出。教授坚持认为它有效。 ...它的工作原理。教授给我发邮件解释说 C 数组是从索引 1 开始的。因为我认为 C 数组是从 0 开始的,所以代码没有意义。现在可以了!【参考方案3】:

我相信你关于i&gt;0 的论点是正确的,不管教授是什么。说。在伪代码中,循环是while i &gt; 0,数组索引从1开始。在C#中,数组索引从0开始,因此你应该有while i &gt;= 0

【讨论】:

对。我还检查了 C 中的数组,它们也以索引 0 开头。【参考方案4】:

我遇到了同样的问题。下面是正确实现上述伪代码的 C 代码。我没有像其他解决方案那样使用指针。

确实,这方面的棘手部分在于伪代码使用基于 1 的数组表示法,这与大多数编程语言不同!

#include <stdio.h>

int main(void)

  int A[] =  50, 20, 10, 40, 60, 30 ;
  int j, key, len, i;
  len = (sizeof(A)) / (sizeof(A[0]));

    for (j = 1; j < 6; j++)   <-- Change here
      key = A[j];
      // Insert key into the sorted sequence A[1 .. j - 1].
      i = j - 1;
      while (i >= 0 && A[i] > key)   <-- Change here
          A[i + 1] = A[i];
          i--;
      
      A[i + 1] = key;
    

    for (int z = 0; z < len; z++) 
       printf("%d ", A[z]);
    
   printf("\n");
 

【讨论】:

【参考方案5】:

我也遇到了你的问题,我找到了解决方案。我在java中编写了如下算法。

int a[] = 5,2,4,3,1;
    int key;
    int i;
    for(int j = 0; j < 5; j++)
    
        key = a[j];
        i = j - 1;

        while(i>=0 && a[i]>key)
        
            a[i+1]= a[i];
            i--;
        
        a[i+1] = key;

        for(int k=0; k<a.length;k++)
        
            System.out.print(a[k]+" ");
        
    

【讨论】:

哇,谢谢你回来(经过这么长时间被问到!)【参考方案6】:

记住:A.length 从 0 到 n,所以 Length 应该是 A.Length -1。我使用那本书用西班牙语为我的 C++ 学生制作了这个算法。用 C# 翻译很简单。

一些翻译,以便您更好地理解

largo = length
actual = current
cadena = chain

void InsertionSort::Sort(char cadena[])

    int largo = strlen(cadena) - 1;
    char actual = '0';
    int i = 0;

    for (int j = 1; j <= largo; j++)
    
        actual = cadena[j];
        i = j - 1;
        while(i >= 0 && cadena[i] > actual)
        
            cadena[i + 1] = cadena[i];
            i--;
        
        cadena[i + 1] = actual;
    

【讨论】:

以上是关于无法从第三版算法介绍中获得插入排序。正确的。我的思维错误在哪里?的主要内容,如果未能解决你的问题,请参考以下文章

《C算法.第1卷,基础数据结构排序和搜索(第三版)》pdf

JSP实用教程(第三版 清华大学出版社)中遇到的问题和解释

第三篇,插入排序算法:直接插入排序希尔排序

插入排序代码

八大经典排序算法

插入排序算法详解及代码实现