1 引言
快速排序,这是面试的时候公司经常会问到的题目,也是让我写了很多遍但就是记不住的家伙,我决心要死磕到底,彻底弄懂快速排序。
2 基本思路
根据教材和各种资料的信息,快速排序是将要排序的数据,先分成两个部分,怎么分呢?
先从数据里面随机选择一个数,这个数,就作为被比较的对象,专业一点的名称叫做主元,由这个主元,将整个数据划分为两部分,一部分都比主元小,另一部分都比主元大。
等于主元的部分可以划分到小的一组,也可以划分为大的一组,前后保持一致就行。
然后,递归地对这两组数据继续前面地操作,直到不可再分为止。
举个例子啊:
2,8,7,1,3,5,6,4
我们随机选择一个数,就最后一个把,4,将数据分为两个部分,一部分是 2,1,3,都小于4,另一部分是 8,7,5,6,都大于4 。
然后,4地位置就是最终它地位置,即第4位,不管后面怎么排序,4始终都在这个位置。
然后,分别对两个序列 2,1,3 和 8,7,5,6 按照上述地规则继续排序即可。
3 示意图
在上代码之前,我们先根据示意图看看怎么通过代码,或者通过步骤来实现这样一个过程。还是用上面地例子,2,8,7,1,3,5,6,4
这里我们会用到4个指针,分别是 p,r,i,j。其中,p表示数组地左边界,r 表示数组地有边界,i 用来标注小于主元地数据,即在 i 左侧的数据都是小于主元的,在 i 右侧的数据都是大于主元的。j 用来进行遍历操作。
初始化的时候,i 需要设置为p-1,j 设置为 p ,主元设置为最后一个元素然后开始进行遍历;
遍历的时候,需要执行操作,具体操作是:
当 j 所在的元素小于主元的时候,i++,然后执行 i 和 j 位置的元素的互换操作。
这一步是比较难理解的,我的解释是这样,为了保证 i 左侧的都是小于主元的元素,右侧都是大于主元的元素,当 j 遍历的时候,遇到小于主元的元素,需要将这个元素换到 i 的左侧去
而 i 的右侧都是大于主元的元素,故执行 i++,再执行交换操作,将小于主元的元素移动到 i 的左侧;
继续遍历 j ,如果 j 指向的元素大于主元,不用进行操作,继续遍历即可;
此时 j 指向 1,小于主元4,执行i++,即i指向8的位置,然后互换 i 和 j 位置的元素,即交换8和1,得到如下:
继续遍历,j 指向3,小于4,执行i++,即i指向7,然后互换 i 和 j 位置的元素,即交换7和3,得到如下:
继续遍历,5和6都小于4,不做操作。遍历完成如下:
然后需要将主元放回它自己的位置,即和i+1 位置的元素进行交换,这个好理解,i左侧的都是小于主元的,当然是和右侧第一个元素互换了。
4 代码
通过上面的过程,我来用C语言实现一下代码。函数应该包含3个,一个是交换函数Swap,一个是划分函数Partition,将数据分为两部分,需要返回的是主元的位置,还有一个就是递归调用的排序函数QuickSort。代码如下:
//交换数组中两个位置对应的元素
void Swap(int *arr, int src, int dst)
{
//位置相同不执行互换操作
if (src != dst)
{
arr[src] = arr[src] ^ arr[dst];
arr[dst] = arr[dst] ^ arr[src];
arr[src] = arr[src] ^ arr[dst];
}
}
//划分函数,将数组根据主元分为两部分
int Partition(int *arr, int left, int right)
{
int i = left-1;
for (int j = left; j < right; j++)
{
//以最后一个数据作为主元,将小于主元的数据交换到最左侧
if (arr[j] <= arr[right])
{
i++;
Swap(arr, i, j);
}
}
//最后需要将主元放到它本身的位置上去
Swap(arr, right, i + 1);
return i + 1;
}
//递归排序函数
void QuickSort3(int *arr, int left, int right)
{
//递归结束的标志,不能再继续划分为止
if (left < right)
{
int p = Partition(arr, left, right);
//这里p-1是因为p的位置已经是主元了,不用再排序了
//如果不执行p-1而执行p,可能会造成死循环,导致栈溢出
QuickSort3(arr, left, p-1);
QuickSort3(arr, p + 1, right);
}
}
我们调用测试一下:
int main()
{
int arr[8] = { 2,8,7,1,3,5,6,4 };
QuickSort3(arr, 0, 7);
for (int i = 0; i < 8; i++)
{
printf("%d\t", arr[i]);
}
printf("\n");
return 0;
}
看下结果图:
再换一个测试方法:
int main()
{
int arr[MAX_NUM] = { 0 };
//使用 0-1000的随机数,1000个数据进行排序测试
srand((unsigned int)time(NULL));
for (int i = 0; i < MAX_NUM; i++)
{
arr[i] = rand() % 1000;
}
QuickSort3(arr, 0, MAX_NUM-1);
for (int i = 0; i < MAX_NUM; i++)
{
if (i % 10 == 0)
{
printf("\n");
}
printf("%d\t", arr[i]);
}
printf("\n");
system("pause");
return 0;
}
测试结果图:
结果有点长,看着部分截图,也可以看出排序没问题。最后,补充一下,最坏的情况下,也就是数据已经排好序了,并且选择最大的数作为主元,时间复杂度是O(n^2),一般情况下,时间复杂度位NlogN。
以上都是个人总结,纯手打,有疏漏之处还请批评指正。还有另外一种快排代码实现,太晚了,改天再写吧,可以看到我的代码里,QuickSort3这个函数,这就意味着还有1和2哈,哈哈。。。