从 10 10 10 年前我开始学 C 语言时我就认为快速排序并不是个简单的算法。相比于归并排序,快速排序,具有非常多的容易出错的实现细节。稍有不慎就可能写出时间复杂度不对甚至不具有正确性的算法。
最近读到一本名为《C++面向对象程序设计》的书,机械工业出版社出版,ISBN 为 9787111656708
,版次为 2020 2020 2020 年 6 6 6 月第 1 1 1 版第 1 1 1 次印刷。经过细致地分析,我发现该书第 17.2.1
节给出的快速排序算法有明显错误,在此发表勘误。
追根溯源,我在该书英文版《C++ Programming: An Object-Oriented Approach》中也发现了同样的问题,英文版 ISBN 为 9780073523385
。
问题一:不一致
该书在算法描述中给出的分划代码实现与完整程序实现中给出的实现不一致,这种不一致导致完整程序中给出的代码明显不能应对被排序数组中存在重复元素的情况。
算法描述部分给出的分划实现
完整程序部分给出的分划实现
我们可以看到,在完整程序代码第 59 59 59 行以及第 66 66 66 行后,相比于算法描述部分的代码缺少了语句 j--
以及 i++
。倘若调用函数 partition
时,arr[i]
与 arr[j]
在初始时具有相同的值,则程序将陷入死循环。
有人可能会质疑,该算法在数组中元素个数互不相同时是否能够正确地得到排序结果,从而猜想原作者只是想编写一个仅适用于不存在重复元素的数组排序的程序。但下文中我们可以证明,即使原始数组中不存在任何重复元素,我们同样可以构造出一个让该程序无法得出正确结果的反例。
问题二:不正确
即使我们按照算法描述中的部分修改了最终的完整程序,该程序仍然不具有正确性,即存在一个数组使得该程序无法正确地将该数组递增排序。下文中给出的代码出了输入输入的方式外,其余部分均与书中提供的算法一致。
#include <iostream>
using namespace std;
void swap(int& x, int& y);
void print(int arr[], int size);
int partition(int arr[], int beg, int end);
void quickSort(int arr[], int beg, int end);
int main() {
// 为了方便测试,我们对原始程序稍加修改,让其从标准输入读入数组 arr 的内容
int n; cin >> n; // 输入待排序数组的元素总数
int* arr = new int[n];
for(int i = 0; i < n; i += 1) {
// 输入待排序数组
cin >> arr[i];
}
cout << "Original array:" << endl;
print(arr, n);
quickSort(arr, 0, n-1);
cout << "Sorted array:" << endl;
print(arr, n);
delete[] arr;
return 0;
}
void swap(int& x, int& y)