就不信学不会【快速排序】

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哈,哈哈。。。

猜你喜欢

转载自blog.csdn.net/kakaluote81/article/details/115339559
今日推荐