指针和数组混淆的 K&R Qsort 示例
我发现很难理解下面的代码片段。 我理解所显示的指向函数风格的指针,但我发现混乱之处在于指示的行中。
void qsort(void **v, int left, int right, int (*comp) (void *, void *))
{
int i, last;
void swap(int **v, int i, int j);
if (left >= right) /* do nothing if array contains */
return; /* fewer than two elements */
swap(v, left, (left + right)/2); /* move partition elem */ [1]
last = left; /* to v[0] */ [2]
for (i = left + 1; i <= right; i++) /* partition */ [3]
if ((*comp) (v[i], v[left]) < 0) [4]
swap(v, ++last, i); [5]
swap(v, left, last); /* restore partition elem */ [6]
qsort(v, left, last - 1); [7]
qsort(v, last + 1, right); [8]
}
有人可以向我解释这个例程,特别是指示的行,只需告诉我它在做什么,因为我无法弄清楚这个 qsort,我在阅读 k&r 时阅读的爱斯基摩指南说 qsort 例程是垃圾,而且过度复杂的。 我只需要理解为什么这样写,因为这对我来说毫无意义。
感谢您阅读本文,即使没有任何意义。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
这是一段漂亮的代码!
首先,了解快速排序背后的想法很重要:
1)获取数字列表。
2) 选择一个,称之为 X。
3) 制作两个列表,其中一个是所有小于 X 的数字,一个是所有大于 X 的数字。
4) 对小于 X 的数字进行排序。对大于 X 的数字进行排序。
这个想法是,如果我们幸运地为 X 选择一个好的值,那么小于 X 的数字列表的大小与大于 X 的数字列表的大小相同如果我们从 2*N+1 个数字开始,那么我们会得到两个包含 N 个数字的列表。 每次,我们希望除以二,但我们必须对 N 个数字进行排序。 我们可以将 N 除以 2 多少次? 这就是 Log(N)。
所以,我们排序 N Log(N) 次。 这很棒!
至于代码是如何工作的,这里是运行过程,还有一些草图。 我们将选择一个小数组:)
这是我们的数组:[DACBE]
在开始,左= 0,指向D。右= 4,指向E。
现在,按照代码,带有您的标签:
[1]交换(v,0,2)[DACBE]→ [CADBE]
我们已经交换了选择的值并将其放在数组的开头。
[2] last=0
这有点聪明......我们想保留一个计数器,这样我们就知道哪些值更大,哪些值更小......你会看到它是如何进展的
[3] for (i=1;i< ;=4;i++)
对于列表中的所有剩余元素...
[4] if ((*comp)(v[i], 'C')<0)
如果 i 处的值小于 'C '...
[5] 交换(v,++last,i);
把它放在列表的开头!
代码
让我们运行 3,4,5 i=1 的
: [CADBE]
if ('A'<'C')
swap('A','A') (AND INCRMENT LAST!)
[CADBE]->; [CADBE](无变化)
last=1
i=2:
[CADBE]
if ('D'<'C')
失败。 继续前行。
i=3:
[CADBE]
if ('B'<'C')
swap('B','D') 最后递增!
[CADBE]-> [CABDE](看它!正在排序!)
last=2
i=4:
[CABDE]
if ('E'<'C')
失败。 继续前行。
好吧,艾斯。 这样循环给出的是 [CABDE], last=2 ('B')
现在第 [6] 行.... swap(left,last)... 那是 swap('C','B')
[CABDE]-> [BACDE]
现在,它的神奇之处在于......它是部分排序的! BA < C< 德!
现在我们对子列表进行排序!
[7]-> [BA]-> [AB]
所以
[BACDE]-> [ABCDE]
[8]-> [DE]->[DE]
所以
[ABCDE]-> [ABCDE]
我们就完成了!
This is a beautiful piece of code!
First off, it's important that you understand the idea behind quicksort:
1) Take a list of numbers.
2) Pick one, call it X.
3) Make two lists, one of all the numbers smaller than X, and one of all the numbers bigger.
4) Sort the numbers smaller than X. Sort the numbers bigger than X.
The idea is that if we get lucky and pick a good value for X, then the list of numbers smaller than X is the same size as the list of numbers bigger than X. If we start with 2*N+1 numbers, then we get two lists of N numbers. Each time, we hope to divide by two, but we have to sort N numbers. How many times can we divide N by two? That's Log(N).
So, we sort N Log(N) times. This is great!
As for how the code works, here's the runthrough, with a little sketch. We'll pick a small array :)
here's our array: [DACBE]
at the start, left=0, pointing to D. right=4, pointing to E.
now, following the code, with your labelling:
[1] swap(v,0,2) [DACBE] -> [CADBE]
we've swapped our chosen value out and put it at the start of the array.
[2] last=0
this is a bit clever... we want to keep a counter so we know which values were greater and which less... you'll see how this progresses
[3] for (i=1;i<=4;i++)
for all the remaining elements in the list...
[4] if ((*comp)(v[i], 'C')<0)
if the value at i is LESS than 'C'...
[5] swap(v,++last,i);
put it at the start of the list!
let's run the code for 3,4,5
i=1:
[CADBE]
if ('A'<'C')
swap('A','A') (AND INCREMENT LAST!)
[CADBE]->[CADBE] (no change)
last=1
i=2:
[CADBE]
if ('D'<'C')
fails. move on.
i=3:
[CADBE]
if ('B'<'C')
swap('B','D') And increment last!
[CADBE] -> [CABDE] (lookit! it's sorting!)
last=2
i=4:
[CABDE]
if ('E'<'C')
fails. move on.
Ok, ace. so that loop gives is [CABDE], last=2 ('B')
Now line [6].... swap(left,last)... that's swap('C','B')
[CABDE] -> [BACDE]
Now, the magic of this is... it's partially sorted! BA < C < DE!
So now we sort the sub-lists!!
[7] -> [BA] -> [AB]
so
[BACDE] -> [ABCDE]
[8]-> [DE]->[DE]
so
[ABCDE] -> [ABCDE]
and we're done!
K&R 的快速排序是优秀编码的一个例子,但不是快速排序工作原理的一个很好的例子。 预交换的目的并不直观,身份交换效率低下且令人困惑。 我编写了一个程序来帮助澄清这一点。 代码注释解释了问题。
我仅在 Linux 下进行编译和测试,但 Visual Studio 对于这个普通的控制台应用程序应该没有问题。
K&R's quick is an example of great coding but not a great example of how quicksort works. The purpose of the preswap is not intuitive and the identity swaps are inefficient and confusing. I have written a program to help clarify this. Code comments explain the issues.
I have compiled and tested only under Linux but Visual Studio should have no problem with this plain vanilla console app.
神奇有用的谷歌关键字:QuickSort
例如 google:how fastsort Works 出现了以下解释:http://www.angelfire.com/pq/jamesbarbetti/articles/sorting/001a_HowQuicksortWorks。 htm等等。
本质上,该代码对指定的左边界和右边界之间的元素应用了快速排序的变体。
对于您已识别的行:
将中间元素与第一个(
左侧
)元素交换。 它将成为“枢轴”。跟踪较大和较小元素之间的边界。 这是枢轴所属的位置。
将其移动到第一个较大元素之前。
将枢轴移回原位。
递归地将 qsort 应用于枢轴之前的元素。 (较小的)
递归地将 qsort 应用于枢轴之后的元素。 (较大的)
尝试自己将代码应用到数字列表中,看看它是否更有意义......
magic useful google keywords: QuickSort
e.g. google:how quicksort works turns up this explanation: http://www.angelfire.com/pq/jamesbarbetti/articles/sorting/001a_HowQuicksortWorks.htm among others.
Essentially, the code applies a variation of quicksort to the elements between the
left
andright
boundaries specified.For the lines you've identified:
swap the middle element with the first (
left
) one. it will become the "pivot".keep track of the boundary between bigger and smaller elements. this is where the pivot belongs.
move it before the first bigger element.
move the pivot back into place.
recursively apply qsort to the elements before the pivot. (the smaller ones)
recursively apply qsort to the elements after the pivot. (the bigger ones)
Try applying the code yourself to a list of numbers, and see if it makes more sense then....
您的代码中有一个错误,末尾的行:
应该是:
或者我遗漏了什么?
此外,重用标准库的名称是一种不好的风格,特别是当新函数的签名与库中的签名不同时。
标准库的函数 qsort 具有以下原型:
如果您的程序有点大(多个目标文件),这可能会产生有趣的错误。 想象一下另一个模块调用标准 qsort 函数,但当您使用兼容的签名但具有不同的语义重新定义它时,您会遇到意外的错误。
There's a bug in your code, the lines at the end:
should be:
Or am I missing something?
Furthermore, it's bad style to reuse names of the standard library, especially if the new function has a different signature than the one in the lib.
The function qsort of the standard library has this prototype:
If your program is a bit bigger (more than one object file) this can give interesting bugs. Imagine another module calling the standard qsort function but as you have redefined it, with a compatible signature, but with a different semantic, you get an unexpected bug.
嗨,我做了第 87 页的示例。也许有人会从中理解。 但在使用此代码之前,请参阅 quicksort
最重要的部分是枢轴(将一只脚放在适当的位置,同时可以自由移动另一只脚)。 我们选择中间元素作为枢轴,将其置于前面,并将其与所有其他元素进行比较。 如果它们小于我们的主元,我们交换它们并仅增加我们的主元位置(注意我们的主元元素仍然位于第一位置)。 完成循环后,我们将枢轴元素(首先)带到这个位置(枢轴位置)。 循环结束后,pivot 之前的所有元素都小于pivot,而pivot 之上的所有元素都大于pivot。 第一次循环时它们没有排序。 因此,您必须再次对枢轴下方和枢轴上方的所有元素递归应用相同的排序算法来对它们进行排序。
Hi I did the example from page 87. May be someone will understand from this. But before going for this code, see quicksort
The most important part is the pivot(put your one feet at place, while free to move other). We choose the middle element as pivot, bring it to front, compare it with all other elements. If they are less than our pivot we swap them and increment only our pivot position (be careful our pivot element still lies at first). After we finish the loop we bring the pivot element(which is at first) to this place (pivot place). After the loop, we have all the elements before pivot less than pivot and all those above pivot greater than pivot. At first loop they are not sorted. So you must again apply same sorting algorithm recursively to all elements below pivot and above pivot to sort them.