无法从第三版算法介绍中获得插入排序。正确的。我的思维错误在哪里?
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 > 0
谓词。实际上每次都差一点。
我的解释(来自我发给教授的电子邮件,懒得把它全部输入):
循环仍然以 5, 1, 2, 3, 4, 6
结束的原因是i > 0
谓词。每次在 while 循环中减去 i 的 1 (i--
)。这最终将导致0 > 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 > 0 && a[i] > 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 >= 0
确实有效。尽管与您的解决方案有些不同,但我确实找到了如何使排序起作用的方法-这是您在其他教科书中经常看到的一种。不用i >= 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>0
的论点是正确的,不管教授是什么。说。在伪代码中,循环是while i > 0
,数组索引从1开始。在C#中,数组索引从0开始,因此你应该有while i >= 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;
【讨论】:
以上是关于无法从第三版算法介绍中获得插入排序。正确的。我的思维错误在哪里?的主要内容,如果未能解决你的问题,请参考以下文章