實現快速排序之時,如果不注意細節,可能遺留一些漏洞。
無限循環
有一種實現看似能夠正常排序,其實存在無限循環的隱患。當左右遊標索引元素與中軸元素相等,由於無法進入內層循環,左右遊標無法移動,導致無限循環。代碼如下所示:
#include <stddef.h>
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 <stddef.h>
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(),遞歸樹深度爲,空間複雜度爲O()。
最壞情況,時間複雜度爲O(/2),遞歸樹深度爲n-1,空間複雜度爲O(n)。
錯誤賦值
也有一種實現在比較元素之時可以不加等於判斷,並且防止無限循環。代碼如下所示:
#include <stddef.h>
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 <stddef.h>
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 <stddef.h>
#include <stdbool.h>
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
無符號整型的兩個極端值得注意,零減一爲最大值,最大值加一爲零,留意下標的比較和運算,代碼形式有所不同。