歸併排序與快速排序的一個實現與理解

cserbo發表於2020-10-18

自己實現的一個快速排序和歸併排序,並且用註釋的方式寫下了自己的理解。算是做一個筆記

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <ctime>

using namespace std;

//先寫歸併排序,下面這兩個函式都可以可以通過傳入回撥函式來定製排序規則
//歸併這個過程裡面兩個陣列的索引起始位置和終止位置很重要,要有正確理解
//放在temp的時候也是從low開始的,因為在排序就是對[low,high]這個閉區間
//來排序的,用到的就是兩個陣列的這個區間的位置

//下面這個merge過程看起來更簡潔,可是理解起來可能有一點難度
void merge(int *nums, int low, int mid, int high, int *temp)
{
    //假設[low, mid]和[mid+1, high]兩個閉區間分別是有序的
    int s = low, t = mid + 1;
    for(int i = low; i <= high; ++i)
    {
        if(s > mid)   //假設這個分支為真,那麼說明i未越界,也即兩部分沒有歸併完,但是前一半沒資料了
                      //那就能說明只有後半部分是還有資料的,對應於下面那個複雜的歸併過程的第三個迴圈
            temp[i] = nums[t++];
        else if(t > high)
            temp[i] = nums[s++];
        else if(nums[s] <= nums[t])
            temp[i] = nums[s++];
        else
            temp[i] = nums[t++];
    }
    //理解了前兩個判斷分支,那麼上面這個歸併過程就沒有那麼難理解了。
    for(int i = low; i <= high; ++i)
    {
        nums[i] = temp[i];
    }
}

void mergeTemp(int *nums, int low, int mid, int high, int *temp)
{
    //假設[low, mid]和[mid+1, high]兩個閉區間分別是有序的
    int s = low, t = mid + 1, k = low;//這裡一定要注意k是從low開始的就行了
    while(s <= mid && t <= high)
    {
        if(nums[s] <= nums[t])
        {
            temp[k++] = nums[s++];
        }
        else
        {
            temp[k++] = nums[t++];
        }
    }

    while(s <= mid)
    {
        temp[k++] = nums[s++];
    }
    while(t <= high)
    {
        temp[k++] = nums[t++];
    }

    for(int i = low; i <= high; ++i)
    {
        nums[i] = temp[i];
    }
}

//temp陣列在整個排序過程中每一個個位置僅僅在一次歸併過程中使用過
void mergeSort(int *nums, int low, int high, int *temp)
{
    if(low >= high) //這裡可以寫一個插入排序或者其他的排序方法來加快速度
        return;
    int mid = low + (high - low) / 2;

    mergeSort(nums, low, mid, temp);
    mergeSort(nums, mid+1, high, temp);

    merge(nums, low, mid, high, temp);
}


/****************************************************
 * 上面是歸併排序,那麼這裡來實現一下快速排序
 * 每一次僅僅只確定一個元素的位置,先對陣列做一個劃分,然後再
 * 對陣列前一半排序,對前一半排序之後再對後一半做一個排序
 * 
 ****************************************************/ 

//這裡假設在使用快排之前已經對陣列做過隨機的打亂
//最好情況是每一次劃分的時候劃分結束時劃分元素都位於
//被劃分的那一部分的最中間部分
void Swap(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
}

void quickSort(int *nums, int low, int high)
{
    if(low >= high) //當陣列只有一個元素的時候已經是有序的了,不需要再排序了。
        return;

    //先對陣列做一個劃分,使用第一個元素
    int v = nums[low]; //v代表劃分元素
    int i = low, j = high;//使用的是閉區間
    while(i < j)  //正是因為下面的這個已經很明白的原因,這裡的判斷條件不等取=,以為i==j的時候就該退出迴圈了
    {   //這個迴圈的終止條件也是很好理解的,i左邊的全部都是小於等於nums[i]的,j
        //右邊的全部都是大於等於nums[j]的,那麼當i==j,說明了上述兩個條件剛好全部
        //都滿足,說明已經達到了預期的劃分效果了
        while(nums[j] >= v && j > i)  //內迴圈的後一個條件不能取等號,因為當取等號的時候還滿足的話,有可能會把v與前面比它小的元素交換
            --j;
        swap(nums[i], nums[j]); //把v換到j當前所在的位置,j原來指向的元素也到了它合適的位置

        while(nums[i] <= v && i < j) //這裡第二個同樣不能取等號,
            ++i;
        swap(nums[i], nums[j]);
    }

    //然後開始遞迴排序,這裡認為k這個位置的元素已經到達了它最終的位置。
    quickSort(nums, low, i-1);
    quickSort(nums, i+1, high);
}

/****************************************************
 * 
 * 對上面兩個排序函式做一個測試,測試好就好
 * 
 * 
 ****************************************************/ 

int main()
{
    srand((unsigned)time(NULL));
    int test[20], temp[20];
    for(int i = 0; i < 20; ++i)
    {
        test[i] = rand() % 200;
    }

    //mergeSort(test, 0, 19, temp);
    quickSort(test, 0, 19);
    for(int i = 0; i < 20; ++i)
    {
        cout << test[i] << " ";
    }
    cout << endl << endl;
    return 0;
}

相關文章