没有一身好内功,招式再多都是空;算法绝对是防身必备,面试时更是不可或缺;跟着算法渣一起从零学算法
快速排序(Quicksort)是对冒泡排序的一种改进
快速排序由C. A. R. Hoare在1962年提出。
它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动
一趟快速排序的算法是:
上面的定义,算法都是很学术的表达,看得有点晕了,看一个示例:
对数组[2,10,8,22,34,5,12,28,21,11]进行快速排序
也可以通过动画更清晰了解整个排序过程
动画:
http://www.zhuxingsheng.com/tools/sort/sort.html
快速排序有好些实现手法,左右指针法、挖坑法、前后指针(快慢指针)法
算法过程与上面的演示图差不多
/**
* 分治法,从小到大排序
* @param array
*/
public static int partition(int []array,int start,int end) {
//取一位做为基数
int pivot = array[end];//这儿取最后一位作为基数了
int left = start;
int right = end-1;//从倒数第二位开始比较
//从两头向中间搜索,从小到大排序
while (left < right) {
//从left端开始搜索
while(left < right && array[left] <= pivot) {
left ++;
}
//从right端开始搜索
while (left < right && array[right] >= pivot) {
right -- ;
}
//两数交换,大的放到右边,小的放到左边
if(left < right) {
swap(array,left,right);
left++;
right--;
}
}
//
if(left != end && array[left] > array[end] ) {
swap(array,left,end);
return left;
}
//right == left
return left+1;
}
/**
* 分治法,从小到大排序
* @param array
* @param start
* @param end
*/
private static void quicSort(int []array,int start,int end){
if( start < end ){
int partition = partition(array,start,end);
print(array);
quicSort(array,start,partition-1);
quicSort(array,partition+1,end);
}
}
挖坑填坑,也可以叫拆东墙补西墙
先演示一番,对[22,12,28,21,14]进行排序
index: | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
data: | 22 | 12 | 28 | 21 | 14 |
第一步:挖最右边的数为坑
坑: pit=array[right]=14;左索引: left=0,右索引: right=3,
index: | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
data: | 22 | 12 | 28 | 21 | X |
从left开始搜索,寻找比坑大的数,发现array[left=0]>14,那就用left=0 填 right=4的坑
array[right=4]=array[left=0];left++;
此时数组:
index: | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
data: | X | 12 | 28 | 21 | 22 |
left=1,right=3
第二步: 从right端开始找比pit小的数,发现array[1] < 14,那就用right=1 填 left=0的坑
array[left=0]= array[right=1];
此时数组:
index: | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
data: | 12 | X | 28 | 21 | 22 |
left=1,right=0; left>right,那此回合结束,用pit填回坑array[left=1]
结束时:
index: | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
data: | 12 | 14 | 28 | 21 | 22 |
发现14左都是小于它的数,右边都是大于它的数
以14为分界,两边数组进行递归下去,就行了
int partion(int arr[], int begin, int end)
{
int pit = arr[end];//挖最右数,留一个坑
int left = begin;
int right = end;
while (left < right)
{
//从最左开始搜索比坑大的数
while (left < right && arr[left] <= pit)
left++;
if (left<right) {
arr[right] = arr[left];//拿left去填坑,left成为新坑
}
while (left < right && arr[right] >= pit)
right--;
if (left < right) {
arr[left] = arr[right];//right去填left坑,left成为新坑
}
}
arr[left] = pit;//用key填坑
return left;
}
void QuickSort(int arr[], int left, int right)
{
if (left < right)
{
int mid = partion(arr, left, right);
print(arr);
QuickSort(arr, left, mid - 1);
QuickSort(arr, mid + 1, right);
}
}
大体思路:
int partion(int arr[], int left, int right)
{
int key = arr[right];//取最后一位为key
int cur = left;//当前指针
int prev = left - 1;//前一个指针
while (cur < right)
{
if(arr[cur] < key && ++prev != cur){//发现比key小的数
swap(arr,cur,prev);
}
cur++;
}
//最后++prev交换
swap(arr,++prev,cur);
return prev;
}
void QuickSort(int arr[], int left, int right)
{
if (left < right)
{
int mid = partion(arr, left, right);
print(arr);
QuickSort(arr, left, mid - 1);
QuickSort(arr, mid + 1, right);
}
}
以上几个方法,不管怎样的思路,都是寻找一个标准数,让它成为一个分界,大于所有左边的数,小于所有右边的数,进行分而治之
最好情况:最在最好的情况下,每次的划分都会恰好从中间将序列划分开来,那么只需要lgn次划分即可划分完成,是一个标准的分治算法Cn=2Cn/2+N,每一次划分都需要比较N次。时间复杂度O(nlogn)
最坏情况:在最坏的情况下,即序列已经排好序的情况下,每次划分都恰好把数组划分成了0,n两部分,那么需要n次划分,但是比较的次数则变成了n, n-1, n-2,….1, 所以整个比较次数约为n(n-1)/2~n2/2
比较和移动次数最少时间复杂度表示为O(nlogn);
比较和移动次数最多的时间复杂度表示为O(n^2);
使用的辅助存储空间最少为logn,最多为n^2;是不稳定的排序;
Best | Average | Worst | Memory | Stable |
---|---|---|---|---|
nlog(n) | nlog(n) | n^2 | log(n) | No |