快速排序的三种实现以及应用场景

好了,废话不多说,直接上干货。
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 (beginwhile (beginarray[begin] <= key)//注意循环内部条件                
                begin++;                    
        while (beginarray[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 (beginwhile (beginarray[begin] <= key)//注意循环内部条件                
            begin++;
        if (begin < end)
        {
            array[end] = array[begin];
            end--;
        }   
        while (beginarray[end] >= key)//注意循环内部条件              
            end--;
        if (beginarray[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 (pPcurif (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 
#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;ielse
    {
        temp_n = b;
        temp_k = k-s;
        for(i=0;ireturn 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小的数是:"<return 0;
}

你可能感兴趣的:(数据结构)