有一种实现看似能够正常排序,其实存在无限循环的隐患。当左右游标索引元素与中轴元素相等,由于无法进入内层循环,左右游标无法移动,导致无限循环。代码如下所示:
#include
void qsort(int *base, int low, int high)
{
if (base == NULL || low >= high \
|| (low | high) <= 0)
return;
int mid = (low + high) / 2;
int key = base[mid];
base[mid] = base[low];
base[low] = key;
int left = low, right = high;
while (left < right)
{
while (left < right && base[right] > key)
--right;
base[left] = base[right];
while (left < right && base[left] < key)
++left;
base[right] = base[left];
}
base[left] = key;
qsort(base, low, left - 1);
qsort(base, left + 1, high);
}
对于此问题,最简单的验证方法是采用相等的元素测试。
而解决方案是增加等于判断,在比较元素大小之时,考虑两个元素相等的情况。代码如下所示:
#include
void qsort(int *base, int low, int high)
{
if (base == NULL || low >= high \
|| (low | high) <= 0)
return;
int mid = (low + high) / 2;
int key = base[mid];
base[mid] = base[low];
base[low] = key;
int left = low, right = high;
while (left < right)
{
while (left < right && base[right] >= key)
--right;
base[left] = base[right];
while (left < right && base[left] <= key)
++left;
base[right] = base[left];
}
base[left] = key;
qsort(base, low, left - 1);
qsort(base, left + 1, high);
}
判断等于虽然可以解决无限循环问题,但是会增加最坏情况出现的概率。
快速排序的空间复杂度主要是递归占用的栈空间。
最好情况,时间复杂度为O( n l o g 2 n nlog_2n nlog2n),递归树深度为 l o g 2 n log_2n log2n,空间复杂度为O( l o g 2 n log_2n log2n)。
最坏情况,时间复杂度为O( n 2 n^{2} n2/2),递归树深度为n-1,空间复杂度为O(n)。
也有一种实现在比较元素之时可以不加等于判断,并且防止无限循环。代码如下所示:
#include
void qsort(int *base, int low, int high)
{
if (base == NULL || low >= high \
|| (low | high) <= 0)
return;
int mid = (low + high) / 2;
int key = base[mid];
base[mid] = base[low];
base[low] = key;
int left = low, right = high;
while (left < right)
{
while (left < right && base[right] > key)
--right;
base[left++] = base[right];
while (left < right && base[left] < key)
++left;
base[right--] = base[left];
}
base[left] = key;
qsort(base, low, left - 1);
qsort(base, left + 1, high);
}
对于此实现代码而言,其实无论是否判断等于,都可能出现结果错乱的现象。
以下列用例测试:
5 9 1 8 7 3 5 2 3 5
排序结果如下所示:
1 2 3 3 5 5 5 7 9 8
当然不仅上述用例出现错误赋值,其他情况也可能出现,究其起因,乃左游标大于右游标之时赋值。
赋值之前判断左右游标可以防止左游标大于右游标之时赋值,代码如下所示:
#include
void qsort(int *base, int low, int high)
{
if (base == NULL || low >= high \
|| (low | high) <= 0)
return;
int mid = (low + high) / 2;
int key = base[mid];
base[mid] = base[low];
base[low] = key;
int left = low, right = high;
while (left < right)
{
while (left < right && base[right] > key)
--right;
if (left < right)
base[left++] = base[right];
while (left < right && base[left] < key)
++left;
if (left < right)
base[right--] = base[left];
}
base[left] = key;
qsort(base, low, left - 1);
qsort(base, left + 1, high);
}
快速排序有多种实现形式,上述无限循环和错误赋值两个问题提出两种实现形式。另外还有更简洁的实现形式,如下所示:
#include
#include
static inline void swap(int *left, int *right)
{
if (left == right)
return;
int temp = *left;
*left = *right;
*right = temp;
}
void qsort(int *base, int low, int high)
{
if (base == NULL || low >= high \
|| (low | high) <= 0)
return;
int mid = (low + high) / 2;
int key = base[mid];
base[mid] = base[low];
base[low] = key;
int left = low, right = high + 1;
while (true)
{
while (left < high && base[++left] < key);
while (base[--right] > key);
if (left >= right) break;
swap(base + left, base + right);
}
swap(base + low, base + right);
qsort(base, low, right - 1);
qsort(base, right + 1, high);
}
此种实现的原理是通过左右游标,分别寻找不小于和不大于中轴元素的两个元素,并且交换两个元素的位置,直到左右游标重叠,让中轴元素归位。
仔细观察以上代码,可以发现都以有符号整型作为数量和下标类型,并且间接依赖于-1,却浪费接近一半的元素数量。
虽然实际应用没必要针对如此大量的元素采用内部排序,而且时间成本和空间成本都非常大,但是数量和下标都是自然数,采用无符号整型可以增加元素容量,让功能变得更加强大,也避免残留范围隐患。
打开以下链接文件,查看针对C语言封装的泛型快速排序:
https://gitee.com/solifree/pure-c/blob/master/算法/排序/quick_sort.c
此泛型快速排序依赖于下述两个文件:
https://gitee.com/solifree/pure-c/blob/master/算法/排序/gtsort.h
https://gitee.com/solifree/pure-c/blob/master/算法/排序/swap.c
无符号整型的两个极端值得注意,零减一为最大值,最大值加一为零,留意下标的比较和运算,代码形式有所不同。