好了,废话不多说,直接上干货。
1、快速排序的概念:
快速排序(Quicksort)是对冒泡排序的一种改进。
快速排序由C. A. R. Horno在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
2、快速排序的三种实现:
/*
way 1:Horno法
算法思想:
1、找一个基准值key(一般三数取中法),本题中基准值为数组中最右的元素,再定义两个指针begin(指向首元素)和end(指向尾元素)
2、begin从前往后走找比基准值key大的元素(找到后停下),end从后往前走找比基准值小的元素(找到后也停下),
然后,交换array[begin]和array[end],依次循环操作。
3、当begin与end相遇,将array[begin]或array[end]与基准值交换。*/
int partion1(int *array,int left,int right)
{
int key = array[right];
int begin = left;
int end = right;
while (begin<end)
{
while (begin<end&&array[begin] <= key)//注意循环内部条件
begin++;
while (begin<end&&array[end] >= key)
end--;
if (begin < end)
std::swap(array[begin], array[end]);
}
if (begin!=right)//防止自己和自己交换,即使自己和自己交换不会出错,但是会多执行以此交换函数,降低效率
std::swap(array[begin], array[right]);
return begin;
}
/*
way2:挖坑法
算法思想:
1、定义begin和end分别指向数据的第一个元素和最后一个元素,基准值key为数组最后一个元素,array[end]元素的位置为一个坑
2、begin从前往后走,找比key大的值,找到之后,将array[begin]赋值给array[end],填充end位置的坑,此时begin位置为一个坑
3、end从后往前走,找比key小的值,找到之后,将array[end]赋值给array[begin],填充begin位置的坑,此时end位置为一个新的坑
4、此类方法依次填坑,当begin和end相遇,则循环结束,将key的值填最后一个坑。
*/
int partion2(int *array, int left, int right)
{
int key = array[right];
int begin = left;
int end = right;
while (begin<end)
{
while (begin<end&&array[begin] <= key)//注意循环内部条件
begin++;
if (begin < end)
{
array[end] = array[begin];
end--;
}
while (begin<end&&array[end] >= key)//注意循环内部条件
end--;
if (begin<end)
{
array[begin] = array[end];
begin++;
}
}
if (begin != right)//防止自己和自己交换,即使自己和自己交换不会出错,但是会多执行以此交换函数,降低效率
std::swap(array[begin], array[end]);
array[begin] = key;
return begin;
}
/*
way3:前后指针法
算法思想:
1、选择一个基准值key,定义两个指针pPre和pPcur(pPre指向pPcur的前一个位置),pPre和pPcur同时走,
当pPcur标记的元素比key大时,只有pPcur继续向前走(此时pPre停下来),当pPcur走到标记的元素比pPre
小时,pPcur停下,此时交换array[pPcur]和array[pPre+1],然后,pPre往前走一步,pPcur继续往前走。
2、当pCur走出界了,则将pPre+1位置的值与key交换。
*/
int partion3(int *array, int left, int right)
{
int pPcur = left;
int pPre = left-1;
int key = array[right];
while (pPcur<right)
{
if (array[pPcur ]< key)
{
swap(array[pPre + 1], array[pPcur]);
pPre++;
}
pPcur++;
}
swap(array[right], array[++pPre]);
return pPre;
}
void QuickSort(int *array,int left,int right )
{
if (left < right)
{
int goal = partion2(array, left, right);
QuickSort(array, left, goal-1);
QuickSort(array, goal+1, right);
}
}
3、快速排序的优化:
//三数取中法:
//快速排序需要找基准值,为了避免找的基准值是最大的数,或者是最小的数,(基准值最大或者最小,效率最差)
//快速排序可用三数取中法优化,以下是三数取中法代码:
int ThreeToMid(int a, int b, int c)//1 3 5
{
if (a>b)
{
if (b > c)
return b;
else
{
if (a < c)
return a;
else
return c;
}
}
else
{
if (b < c)
return b;
else
{
if (a>c)
return a;
else
return c;
}
}
}
4、性能分析:
(1)若选择的基准值是数据中最大(小)的数,但是要排成降序(升序),则性能最低,为了改变这种特殊情况,故选择基准值的时候采用了三数取中法。
(2)将升序序列(降序)排成降序(升序)序列,效率也是最低的。
(3)若每以基准值划分过程产生的区间大小都为n/2,则快速排序法运行就快得多了。
综上所述,快速排序的最好情况下的时间复杂度为O(N*logN),最坏情况下的时间复杂度为O(N^2),平均的时间复杂度为O(N*logN)。空间复杂度为O(1),当然了,快速排序是属于不稳定排序。
综合以上情况的对比,快速排序适用于数据杂乱无章的场景,而且越乱,快速排序的效率越体现的淋漓尽致。
5、应用场景:
------------求数组中第k小的数------------
加入数组arr中数据是:4,0,1,0,2,3,那么第三小的元素是1,问怎么样快速的求出这个第k小的数。
解法一:
利用STL中快速排序把数组进行排序,然后数组下标是k-1的就是我们要求的第k小的元素了,这种情况的时间复杂度接近于O(n*logn)。但是回头想一想这个算法是把数组进行了全排序,而我们只是要找到数组中第k小的数,这显然是“杀猪用了牛刀”。当然了时间复杂度较高也是正常的。
解法二:
想一想快速排序的思想:将数组中某一个元素m作为划分依据(我们假设为第一个元素,即m = arr[0]),遍历一遍数组,使得数组的格局变成这样的三个部分:(1)m前面的元素 (2)m (3)m后面的元素。其中m前面的元素小于m,m后面的元素大于m,这样找第k小的数正好可以借鉴这个思想,即:
a、若m前面的元素个数大于k,则第k小的数一定在m前面的元素中,这时我们只需要继续在m前面的元素中搜索第k小的数;
b、若m前面的元素个数小于k,则第k小的数一定在m后面的元素中,这是我们只需要继续在m后面的元素中搜索第k-s小的数,其中s是m前面的元素个数。
代码如下:
#include <iostream.h>
#define MAX_SIZE 100
int Biger[MAX_SIZE];
int Smaller[MAX_SIZE];
int Select_kth_Small(int arr[],int n,int k)
{
if(n == 1)
return arr[0];
int b = 0,s = 0,t = arr[0],temp_n,temp_k;
int temp[MAX_SIZE];
for(int i = 1 ; i < n ; i++)//遍历集合
{
if(arr[i] > t)
Biger[b++] = arr[i]; //如果当前元素比t大,就将当前元素加入Biger[]
else
Smaller[s++] = arr[i];//反之就加入到Smaller,这里没有考虑set[0]
}
if(b == 0)
{
Biger[b++] = t;//if...else主要是为了防止t大于或小于其他所有元素的情况
}
else
{
Smaller[s++] = t;
}
//如果Smaller集合中的元素个数大于K,说明第K小的元素必在其中
//否则一定在Biger中,且应该是Biger集合中第k-r小的元素
//更新相应的变量
if(s >= k)
{
temp_n = s;
temp_k = k;
for(i=0;i<temp_n;i++)
{
temp[i] = Smaller[i];
}
}
else
{
temp_n = b;
temp_k = k-s;
for(i=0;i<temp_n;i++)
{
temp[i]=Biger[i];
}
}
return Select_kth_Small(temp,temp_n,temp_k);
}
int main()
{
int arr[]={4,0,1,0,2,3};
int ans = Select_kth_Small(arr,6,3);
cout<<"在数组arr[]={4,0,1,0,2,3}中,第3小的数是:"<<ans<<endl;
return 0;
}