1. 程式人生 > >七大排序演算法(5)------快速排序(遞迴和非遞迴)

七大排序演算法(5)------快速排序(遞迴和非遞迴)

         在本文中使用到的升序,降序,交換函式的程式碼見:這篇部落格

快速排序(遞迴實現)

快速排序的基本思想是在待排序序列中找到一個基準值(一般取待排序序列的最後一個元素),然後將該基準值放置在一個合適的位置,使得在基準值之前的元素都小於等於基準值,基準值之後的元素都大於等於基準值。

        然後再對基準值之前的序列使用上述方法進行排序尋找基準值位置,對基準值之後的序列使用上述方法進行排序尋找基準值位置。所以這是一個遞迴的過程。

        根據這個合適的位置尋找的方式可以分為兩種方法來實現。一種是交換法,一種是挖坑法。

1. 交換法實現快速排序

  排序思路如下:

  所以將上述的過程1封裝為一個函式,該函式的實現就是不斷的交換的過程,因此及該方法也成為交換法:

(1)首先對待排序序列使用該函式,使基準值之前的元素小於等於基準值,基準值之後的元素大於等於基準值,同時返回基準值所在的下標位置left;

(2)然後對left之前的序列進行(1)~(3)的過程;

(3)再對left之後的序列進行(1)~(3)的過程;

        所以,上述的(1)~(3)可以作為遞迴函式的過程,當待排序序列的元素個數為小於等於1時,遞迴函式返回,不用再進行(1)~(3)的過程。

        注意:

1. 如果選取的基準值為最後一個元素,則right指標先走,left指標後走。(無論升降序)

2. 如果選取的基準值為第一個元素,此時left指標就要先走,right指標後走(無論升降序)。

         總之,無論是升序排序,先走的指標為與基準值對立的位置。

        程式碼實現如下:

void QuickSort(int arr[],int size)
{
    if(arr == NULL || size <= 1)
    {
        return;
    }
    int left = 0;
    int right = size;
    
    _QuickSort(arr,left,right);
}
//遞迴函式                                                                                                      
void _QuickSort(int arr[],int left,int right)
{
    if(arr == NULL || right - left <= 1)
    {
        return;
    }
    //先找一個基準值,這裡設定陣列最後一個元素基準值
    //找到放置基準值的位置,使得在該位置之前的陣列元素小於等於基準值
    //在該位置之後的陣列元素大於等於基準值
    int mid = Pattern2(arr,left,right);
   
    _QuickSort(arr,left,mid);
    _QuickSort(arr,mid + 1,right);
}

        交換函式為:

//交換法
int Pattern1(int arr[],int left,int right)
{
    //該函式用於給基準值找一個合適的位置,
    //使得在基準值之前的元素都小於等於基準值
    //在基準值之後的元素都大於等於基準值
    //所以要先從前往後遍歷使陣列前面的元素小於等於基準值
    //從後往前遍歷使後面的元素大於等於基準值
    //當兩個遍歷索引相遇時,一定找到了小於基準值和大於據準值元素的分界點
    //此時將分界點處的值替換為基準值即可保證上述的要求


    //如果陣列只有一個元素,則基準值直接放置在起始位置即可
    if(right - left <= 1)
    {
        return left;
    }
    //從起始位置開始往後遍歷尋找大於基準值的陣列元素
    int left_index = left;
    int right_index = right - 1;                                                                                
    //從末尾位置開始往前遍歷尋找小於基準值的陣列元素
    //將基準值設定為所在區間的最後一個元素值
    int basic_value = arr[right - 1];

    while(left_index < right_index)
    {
        //如果從前往後找到大於基準值的元素,就停下來
        while(left_index < right_index && arr[left_index] <= basic_value)
        {
            left_index++;
        }
        //如果從後往前找到小於基準值的元素,就停下來
        while(left_index < right_index && arr[right_index] >= basic_value)
        {
            right_index--;
        }
        //交換停下來的兩個值
        Swap(&arr[left_index],&arr[right_index]);
    }
    Swap(&arr[left_index],&arr[right - 1]);
    return left_index;
}

2. 挖坑法實現快速排序

該方法在實現是:

(1)先將基準值之前的元素小於等於基準值,之後的元素大於等於基準值。

(2)然後對基準值之前的序列呼叫遞迴函式

(3)最後對基準值之後的序列呼叫遞迴函式。

        只是在(1)的實現上與上述的交換法不同,如下圖:

        所以上述過程可以實現為:

//挖坑法
int Pattern2(int arr[],int left,int right)
{
    if(right - left <= 1)
    {
        return left;                                                                                            
    }
    int left_index = 0;
    int right_index = right - 1;
    //基準值為帶排序序列的最後一個元素
    int basic_value = arr[right - 1];
    //當left_index和right_index沒有相遇時
    while(left_index < right_index)
    {
        //如果沒有相遇且left_index遇到小於等於基準值的元素,則left_index後移
        while(left_index < right_index && arr[left_index] <= basic_value)
        {
            left_index++;
        }
        //如果沒有相遇且left_index遇到大於基準值的元素
        if(arr[left_index] > basic_value)
        {
            //將該元素賦值到坑所在位置
            arr[right_index] = arr[left_index];
        }
        //同理
        while(left_index < right_index && arr[right_index] >= basic_value)
        {
            right_index--;
        }
        if(arr[right_index] < basic_value)
        {
            arr[left_index] = arr[right_index];
        }
    }
    //當left_index與right_index相遇時,一定位於坑所在的位置
    //此時,將基準值賦值到該處即可
    arr[left_index] = basic_value;                                                                              
    //返回基準值放置的位置
    return left_index;}}

非遞迴實現快速排序

        非遞迴的實現與上述思路相同。都是相對整個序列進行交換法或挖坑法進行處理,然後對基準值之前的序列進行相同的處理,在對基準值之後的序列進行處理。

        因為在對基準值之前或之後的序列進行處理時,還可能再分為兩部分的序列,所以這裡藉助棧來儲存待處理序列的首尾下標:

(1)將整個待排序序列首尾下標入棧;

(2)取棧頂元素,如果棧頂元素為空,說明待排序序列處理完了,此時直接返回;

        如果不為空,則該棧頂元素必為帶排序序列的尾座標。出棧後,再取棧頂元素,該棧頂元素必為待排序序列的首座標,然後 再出棧,如果待排序序列的元素個數小於等於1,此時不用再進行下面的處理,重新回到(2)進行新一輪的處理;

(3)將上述的兩個首尾座標利用交換法或挖坑法進行處理,得到基準值的座標;

(4)將基準值之前的序列首尾座標分別入棧;

(5)將基準值之後的序列首尾座標分別入棧;

(6)重複(2)~(5)的操作。

        以下有關的棧的操作實現見:這篇部落格

        根據上述思路,程式碼實現如下:

void QuickSortByLoop(int arr[],int size)
{
    if(arr == NULL || size <= 1)                                                                                
    {
        return;
    }

    SeqStack stack;//定義棧
    InitSeqStack(&stack);//初始化棧
    //入棧整個待排序序列的首位座標
    SeqStackPush(&stack,0);
    SeqStackPush(&stack,size);

    int left;
    int right;

    while(1)
    {
        //取棧頂元素作為帶排序序列的尾座標
        int ret = SeqStackTop(&stack,&right);
        if(ret == -1)//棧定為空,帶排序序列處理完
        {
            return;
        }
        //出棧後再取棧頂元素
        SeqStackPop(&stack);
        //此時的棧定元素必為帶排序序列的首座標
        SeqStackTop(&stack,&left);
        //出棧棧頂元素
        SeqStackPop(&stack);
        //如果帶排序序列元素個數小於等於1,則該序列必然已經排好序
        //且不能在分為更小的序列,也就不需要再進行排序再進行入棧了
        if(right - left <= 1)
        {
            continue;
        }
        //對帶排序序列進行排序處理
        int mid = Pattern1(arr,left,right);
        //將帶排序序列分為兩部分分別入棧,進行處理
        SeqStackPush(&stack,left);
        SeqStackPush(&stack,mid);
        SeqStackPush(&stack,mid + 1);
        SeqStackPush(&stack,right);                                                                             
    }
    return;
}

        快速排序的時間複雜度為:O(nlogn)(平均時間複雜度),O(n^2)(最壞時間複雜度)

                        最快空間複雜度:O(n)

                        穩定性:不穩定