快速排序之代码问题

快速排序之代码问题

    • 无限循环
    • 错误赋值
    • 不同形式
    • 最大容量

实现快速排序之时,如果不注意细节,可能遗留一些漏洞。

无限循环

有一种实现看似能够正常排序,其实存在无限循环的隐患。当左右游标索引元素与中轴元素相等,由于无法进入内层循环,左右游标无法移动,导致无限循环。代码如下所示:

#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
无符号整型的两个极端值得注意,零减一为最大值,最大值加一为零,留意下标的比较和运算,代码形式有所不同。

你可能感兴趣的:(C/C++,算法)