快速排序是我们在面试过程中经常遇到的一个题目,而且在这个算法中包含了一个很重要的思想,那就是分治思想(D&C)。所以学习快速排序之后,当我们以后遇到问题,就可以问一问自己,“使用分而治之能解决吗??”。
我们的学习顺序是:首先要理解递归(前面已经学习过),然后理解分治思想,最后再学习快速排序。下面我们开始学习:
1. 理解分而治之
例一:假设你的女朋友今天想要吃红烧豆腐,但是你的女朋友有强迫症,要求你在切豆腐时切成一样大的方块,而且要保证切出的方块是最大的。听见这个要求是不是有种想要口吐芬芳的冲动,罢了,还是乖乖照做吧,不然又说你是不是不爱我了。。。下面你去市场买了一块豆腐,回家测量发现豆腐长168mm,宽64mm,现在就想办法达到女朋友的要求。下面开始切豆腐:
首先你可能会想到上面三种切法,但是这样切是达不到要求的。第一种不是正方形,第二种切的太小,第三种切的大小不同。
那么到底应该怎样切才合适呢?这就用到分而治之的思想
使用分治思想解决问题包括两个步骤:
- 找出基线条件,这种条件必须尽可能简单。
- 不断地将问题分解,也就是不断地缩小问题的规模,知道符合基线条件。
首先我们来找基线条件:在这个问题里面,我们很容易想到一种情况就是一条边的长度如果是另一条边长度的整数倍,那就好办多了,直接在长边上面切一刀就变成两个正方形。所以这个问题的基线条件就是一条边的长度是另一条边长度的整数倍。
然后我们缩小问题的规模:对于一个168mm * 64mm的豆腐,根据基线条件,可以得出以下切法
如图所示这块豆腐宽仍然是64mm,长被我们分成64+64+40,这样就可以先切出两个方块,然后我们很容易想到剩下的这块豆腐(如蓝色区域所示)肯定可以使用相同的算法进行切方块啊!这样我们就把问题的规模缩小了,把切168 * 64换成 64 * 40。
下面我们再次使用相同的方法:64可以拆成40 + 24,这样我们就可以再切一个方块,变成40 * 24。40又可以拆成24 +16,变成24 * 16。24又可以拆成16 + 8,变成16 * 8。此时,注意重点来了,这就达到了上面提到的基线条件。所以,你最大只能切成8 * 8的正方形豆腐块,接下来就要考验一下你的刀功咯。
例二: 我们给定一个int类型的数组,int a[3] = {2, 4, 6};求这个数组的所有元素之和?
要解决这个问题很多人的第一反应是用循环,当然,这样是很容易。但如果要求使用递归函数来解决,那么应该怎么办呢? - 第一步:和上面一样先找基线条件。思考一下,要解这个问题最简单的情况应该是什么样呢?是不是最好数组中一个元素都没有或者只有一个元素,这样我们计算所有元素之和将不费吹灰之力。所以这就是这个问题的基线条件。
- 第二步:同样是缩小问题规模。想一想,是不是我们每拿出数组中的一个元素求和之后,我们离答案就更近一步,这样说明我们每次递归调用都离空数组就更近一步。
sum(2, 4, 6) 等同于 2 + sum(4, 6);
这两种情况结果一样都是12,但是第二种情况给sum函数传递的数组更短,换一种说法,这就缩小了问题的规模。
综上所述,这个问题的解决过程应该是:
sum函数接收一个数组 -----> 如果数组为空,则返回0;
|
-----> 如果数组不为空,计算除数组第一个元素其他元素的和,然后将结果加上第一个元素,最后返回结果。
这就是递归的过程,用的也是分治的思想。
2. 学习快速排序
前面也提到,快速排序也就是用的分治思想。话不多说,直接上实例:
假设有一个包含5个元素的int型数组,3、5、2、1、4,利用快速排序的方法进行从小到大排序。
- 首先就是选择基准值,基准值可以在数组中随意挑选,所以我们就有5种选择。
假设用3作为基准值,就把比3大的放在3的右边,把比3小的放在3的左边。排序情况如下:
qsort([2, 1]) 3 qsort([5, 4]) ,得到另两个子数组,然后再分别排序,得到结果--------->[1,2] 3 [4,5],合并之后就得到一个有序数组。
假设用5作为基准值,排序情况如下:
qsort([3, 2, 1, 4]) 5 qsort([ ]) ,同样得到两个子数组,只不过右边为空数组而已,得到结果 ---------> [1,2 ,3, 4] 5,合并之后得到一个有序数组。
依次类推,这就是快速排序。
最后我们看一下c语言代码:
#include <stdio.h>
#include <stdlib.h>
/*数组打印函数*/
void display(int array[], int maxlen)
{
int i;
for(i = 0; i < maxlen; i++)
{
printf("%-3d", array[i]);
}
printf("\n");
return ;
}
/*快速排序算法*/
void QuickSort(int *arr, int low, int high)
{
if (low < high)
{
int i = low;
int j = high;
int k = arr[low];
while (i < j)
{
while(i < j && arr[j] >= k) // 从右向左找第一个小于k的数
{
j--;
}
if(i < j)
{
arr[i++] = arr[j];
}
while(i < j && arr[i] < k) // 从左向右找第一个大于等于k的数
{
i++;
}
if(i < j)
{
arr[j--] = arr[i];
}
}
arr[i] = k;
// 递归调用
QuickSort(arr, low, i - 1); // 对k左边的子数组进行排序
QuickSort(arr, i + 1, high); // 对k右边的子数组进行排序
}
}
/*main函数*/
int main()
{
int array[5] = {3, 5, 2, 1, 4};
printf("排序前的数组\n");
display(array, 5);
QuickSort(array, 0, 5-1); // 快速排序
printf("排序后的数组\n");
display(array, 5);
return 0;
}