快速排序法-Quick Sort

快速排序法-Quick Sort

【算法思想】
(1)在当前数组中选择一个数为基点,使它处于排好序的位置,并且这个数左边的数小于它,右边的数大于它。
在这里插入图片描述
(2)分别对这个基点左右两边的数进行相同操作。
【Partition】
在这里插入图片描述
(1)通常选取数组最左边的数作为分界点元素,这里我们标记为l,然后我们开始遍历剩余的数组,将其分为小于v的部分和大于v的部分,这两个部分的分界点我们用j来表示。当前访问的元素我们记为i。这样arr[l+1…j]<v,arr[j+1…i-1]>v。
(2)下面开始分两种情况去讨论当前元素i如何变化才能保证数组保持这种性质。如果当前元素是比v还要大的,即e>v,这个元素直接放在>v部分的后面即可,此时i++,继续考虑下一个元素即可。在这里插入图片描述
在这里插入图片描述
如果当前元素小于v,则需要将j+1位置的元素和i位置的元素进行交换,j++,然后i++继续考虑下一个元素。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(3)最后,只需要将l位置的元素和j位置的元素交换位置即可。
在这里插入图片描述
在这里插入图片描述
【代码】
<quick_sort.cpp>

#include <iostream>
#include <algorithm>
#include "MergeSort.h"
#include "SortTestHelper.h"
using namespace std;
//对arr[l...r]部分进行partition操作
//返回p,使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
template <typename T>
int __partition(T arr[], int l, int r) {
    swap(arr[l], arr[rand()%(r-l+1)+l]);
    T v = arr[l];
    //arr[l+1...j] < v ; arr[j+1...i) > v
    int j = l;
    for(int i = l + 1; i <= r; i++) {
        if(arr[i] < v) {
            swap(arr[j+1], arr[i]);
            j++;
        }
    }
    swap(arr[l], arr[j]);
    return j;
}
//对arr[l...r]部分进行快速排序
template <typename T>
void __quickSort(T arr[], int l, int r) {
    /*
    //当数据量较小时,可以使用插入排序做优化
    if(l >= r) {
		insertionSort(arr, l, r);
		reuturn;
	}
	*/
    if(l >= r)
        return;
    int p = __partition(arr, l, r);
    __quickSort(arr, l, p - 1);
    __quickSort(arr, p + 1, r);
}
template <typename T>
void quickSort(T arr[], int n) {
    __quickSort(arr,0, n - 1);
}

int main() {
    int n = 1000000;
    cout << "Test for Random Array, size = " << n << ", random range [0, " << n << "]" << endl;
    int* arr1 = SortTestHelper::generateRandomArray(n, 0, n);
    int* arr2 = SortTestHelper::copyIntArray(arr1, n);
    SortTestHelper::testSort("Merge Sort", mergeSort, arr1, n);
    SortTestHelper::testSort("Quick Sort", quickSort, arr2, n);
    delete[] arr1;
    delete[] arr2;
    return 0;
}

<MergeSort.h>

#ifndef SORTALGORITHM_MERGESORT_H
#define SORTALGORITHM_MERGESORT_H
#include <iostream>
#include <algorithm>
#include "SortTestHelper.h"
#include "InsertionSort.h"
using namespace std;
//将arr[l...mid]和arr[mid+1...r]两部分进行归并
template <typename T>
void __merge(T arr[], int l, int mid, int r) {
    T aux[r - l + 1];
    for(int i = l; i <= r; i++) {
        aux[i - l] = arr[i]; //辅助数组
    }
    int i = l, j = mid + 1;
    for(int k = l; k <= r; k++) {
        if(i > mid) {
            arr[k] = aux[j - l];
        }
        else if(j > r) {
            arr[k] = aux[i - l];
            i++;
        }
        else if(aux[i - l] < aux[j - l]) {
            arr[k] = aux[i - l];
            i++;
        }
        else {
            arr[k] = aux[j - l];
            j++;
        }
    }
}
//递归使用归并排序,对arr[l...r]的范围进行排序
template <typename T>
void __mergeSort(T arr[], int l, int r) {
//    if(l >= r)
//        return;
//引入插入排序改进归并排序
    if(r - l <= 15) {
        insertionSort(arr, l, r);
        return;
    }
    int mid = (l + r) / 2;
    __mergeSort(arr, l, mid);
    __mergeSort(arr, mid + 1, r);
    if(arr[mid] > arr[mid + 1])
        __merge(arr, l, mid, r);
}

template <typename T>
void mergeSort(T arr[], int n) {

    __mergeSort(arr, 0, n - 1);
}
#endif //SORTALGORITHM_MERGESORT_H

<SortTestHelper.h>

#ifndef SELECTIONSORT_SORTTESTHELPER_H
#define SELECTIONSORT_SORTTESTHELPER_H

#include <iostream>
#include <ctime>
#include <cassert>
using namespace std;
namespace SortTestHelper {
    int* generateRandomArray(int n, int rangeL, int rangeR) {
        assert(rangeL <= rangeR);
        int *arr = new int[n];
        srand(time(NULL));
        for(int i = 0; i < n; i++) {
            arr[i] = rand() % (rangeR - rangeL + 1) + rangeL;
        }
        return arr;
    }
    template <typename T>
    void printArray(T arr[], int n) {
        for(int i = 0; i < n; i++) {
            cout << arr[i] << " ";
        }
        cout << endl;
        return;
    }
    template <typename T>
    bool isSorted(T arr[], int n) {
        for(int i = 0; i < n - 1; i++) {
            if(arr[i] > arr[i + 1])
                return false;
        }
        return true;
    }

    template <typename T>
    void testSort(string sortName, void(*sort)(T[], int), T arr[], int n) {
        clock_t startTime = clock();
        sort(arr, n);
        clock_t endTime = clock();
        assert(isSorted(arr, n));
        cout << sortName << " : " << double(endTime - startTime) / CLOCKS_PER_SEC << " s" << endl;
        return;
    }

    int* copyIntArray(int a[], int n) {
        int* arr = new int[n];
        copy(a, a+n, arr);
        return arr;
    }

    int *generateNearlyOrderedArray(int n, int swapTimes) {
        int *arr = new int[n];
        for(int i = 0; i < n; i++) {
            arr[i] = i;
        }
        srand(time(NULL));
        for(int i = 0; i < swapTimes; i++) {
            int posx = rand()%n;
            int posy = rand()%n;
            swap(arr[posx], arr[posy]);
        }
        return arr;
    }
}
#endif //SELECTIONSORT_SORTTESTHELPER_H

【随机化快速排序】
【注】当对近乎有序的数组进行排序是,快速排序的效率比归并排序效率低很多。
在这里插入图片描述
归并排序是将一个数组不断地一分为二,一共log(n)层,处理每层需要n,所以是O(nlogn)级别的算法。
在这里插入图片描述
归并排序是将一个数组平均分为两个部分的,而快速排序却不能保证均分,导致整个递归树平衡性很差,并且我们不能保证这棵树的高度是log(n),当对一个完全有序的数组进行快速排序,将退化为O(n2)级别的算法,递归树的高度为n,处理每层需要n。
在这里插入图片描述
【改进方法】
随机基准点,而不是每次以数组第一个元素为基准点,这样该算法退化为O(n2)的可能性几乎为0。
【随机化快速排序法代码】

//对arr[l...r]部分进行partition操作
//返回p,使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
template <typename T>
int __partition(T arr[], int l, int r) {
    swap(arr[l], arr[rand()%(r-l+1)+l]);
    T v = arr[l];
    //arr[l+1...j] < v ; arr[j+1...i) > v
    int j = l;
    for(int i = l + 1; i <= r; i++) {
        if(arr[i] < v) {
            swap(arr[j+1], arr[i]);
            j++;
        }
    }
    swap(arr[l], arr[j]);
    return j;
}
//对arr[l...r]部分进行快速排序
template <typename T>
void __quickSort(T arr[], int l, int r) {
    if(l >= r)
        return;

    int p = __partition(arr, l, r);
    __quickSort(arr, l, p - 1);
    __quickSort(arr, p + 1, r);
}
template <typename T>
void quickSort(T arr[], int n) {
    srand(time(NULL));
    __quickSort(arr,0, n - 1);
}

【双路快速排序算法】
当一个数组中包含着大量重复键值的时候,如上的快速排序算法很大可能将数组分为极为不平衡的两部分,从而导致算法退化为O(n2)。
【改进思路】
在这里插入图片描述
将小于v的部分和大于v的部分置于数组的两端,i从左向右寻找比v大的元素,j从右向左找比v小的元素,直到i和j都找到对应的键值,此时交换i和j所指向的元素,i++、j++继续此过程,直到i>=j为止在这里插入图片描述
橙色和紫色部分都包含了等于v的元素,这样等于v的元素就不会大量集中于一个部分,从而能把大量等于v的元素平分开来,不会导致两部分切分后差距过大。
【双路快速排序法代码】

//对arr[l...r]部分进行partition操作
//返回p,使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
template <typename T>
int __partition2(T arr[], int l, int r) {
    swap(arr[l], arr[rand()%(r-l+1)+l]);
    T v = arr[l];
    //arr[l+1...i) <= v; arr(j...r] >= v
    int i = l + 1, j = r;
    while(true) {
        while(i <= r && arr[i] < v) {
            i++;
        }
        while(j >= l + 1 && arr[j] > v) {
            j--;
        }
        if(i > j)
            break;
        swap(arr[i], arr[j]);
        i++;
        j--;
    }
    swap(arr[l], arr[j]);
    return j;
}
//对arr[l...r]部分进行快速排序
template <typename T>
void __quickSort2(T arr[], int l, int r) {
    if(l >= r)
        return;
    int p = __partition2(arr, l, r);
    __quickSort2(arr, l, p - 1);
    __quickSort2(arr, p + 1, r);
}

template <typename T>
void quickSort(T arr[], int n) {
    srand(time(NULL));
    __quickSort2(arr,0, n - 1);
}

【三路快速排序法】- Quick Sort 3 Ways
【注】当处理含有大量重复键值的数组时,三路快速排序算法优势比较明显。
在这里插入图片描述
三路快速排序算法将数组分为了三个部分,即大于v、小于v和等于v。当前元素如果等于v时,如上图所示,则直接加在等于v那部分的后面,i++考虑下一个位置的元素。
在这里插入图片描述
当前元素如果小于v,则只需要将lt+1位置的元素和i位置的元素交换位置,lt向右移一位即可,i++考虑下一个元素。
在这里插入图片描述
如果当前元素的值大于v,只需要将i位置的元素和gt-1位置的元素交换位置即可,gt向左移一位,i不动,此时i位置的元素是原来gt-1位置的元素。
在这里插入图片描述
当gt和i重合时,本次快速排序操作结束。
在这里插入图片描述
最终还要将l和lt位置的元素交换位置,此时等于v的部分已经放在了最终整个数组排好序的位置,只需要对小于v和大于v部分进行递归的快速排序即可。
在这里插入图片描述
在这里插入图片描述
【注】这样做的好处是不需要对大量等于v的元素重复操作,一次性少考虑很多元素,如果等于v的元素非常多的话,优化效果将会很明显。
【三路快速排序算法代码】

#include <iostream>
#include <algorithm>
#include "MergeSort.h"
#include "InsertionSort.h"
#include "SortTestHelper.h"
#include "QuickSort.h"
using namespace std;

//三路快速排序处理arr[l...r]
//将arr[l...r]分为<v ; ==v ; >v 三部分
//之后递归对 <v; > v 两部分进行三路快速排序
template <typename T>
void __quickSort3Ways(T arr[], int l, int r) {
    if(r - l <= 15) {
        insertionSort(arr, l, r);
        return;
    }
    //partition
    swap(arr[l], arr[rand()%(r-l+1)+l]);
    T v = arr[l];
    int lt = l; //arr[l+1...lt] < v
    int gt = r + 1; //arr[gt...r] > v
    int i = l + 1; //arr[lt+1...i) == v
    while(i < gt) {
        if(arr[i] < v) {
            swap(arr[i], arr[lt+1]);
            lt++;
            i++;
        }
        else if(arr[i] > v) {
            swap(arr[i], arr[gt-1]);
            gt--;
        }
        else {
            //arr[i] == v
            i++;
        }
    }
    swap(arr[l], arr[lt]);
    __quickSort3Ways(arr, l, lt-1);
    __quickSort3Ways(arr, gt, r);
}
template <typename T>
void quickSort3Ways(T arr[], int n) {
    srand(time(NULL));
    __quickSort3Ways(arr, 0, n-1);
}
int main() {
    int n = 1000000;
    //测试1 一般性测试
    cout << "Test for Random Array, size = " << n << ", random range [0, " << n << "]" << endl;
    int* arr1 = SortTestHelper::generateRandomArray(n, 0, n);
    int* arr2 = SortTestHelper::copyIntArray(arr1, n);
    int* arr3 = SortTestHelper::copyIntArray(arr1, n);
    SortTestHelper::testSort("Merge Sort", mergeSort, arr1, n);
    SortTestHelper::testSort("Quick Sort", quickSort, arr2, n);
    SortTestHelper::testSort("Quick Sort 3 Ways", quickSort3Ways, arr3, n);
    delete[] arr1;
    delete[] arr2;
    delete[] arr3;
    cout << endl;
    //测试2 测试近乎有序的数组
    int swapTimes = 100;
    cout << "Test for Random Nearly Ordered Array, size = " << n << ", random range [0, " << n << "] , swap time = " << swapTimes << endl;
    arr1 = SortTestHelper::generateNearlyOrderedArray(n, swapTimes);
    arr2 = SortTestHelper::copyIntArray(arr1, n);
    arr3 = SortTestHelper::copyIntArray(arr1, n);
    SortTestHelper::testSort("Merge Sort", mergeSort, arr1, n);
    SortTestHelper::testSort("Quick Sort", quickSort, arr2, n);
    SortTestHelper::testSort("Quick Sort 3 Ways", quickSort3Ways, arr3, n);
    delete[] arr1;
    delete[] arr2;
    delete[] arr3;
    cout << endl;
    //测试3 测试存在包含大量相同元素的数组
    cout << "Test for Random Array, size = " << n << ", random range [0, 10]" << endl;
    arr1 = SortTestHelper::generateRandomArray(n, 0, 10);
    arr2 = SortTestHelper::copyIntArray(arr1, n);
    arr3 = SortTestHelper::copyIntArray(arr1, n);
    SortTestHelper::testSort("Merge Sort", mergeSort, arr1, n);
    SortTestHelper::testSort("Quick Sort", quickSort, arr2, n);
    SortTestHelper::testSort("Quick Sort 3 Ways", quickSort3Ways, arr3, n);
    delete[] arr1;
    delete[] arr2;
    delete[] arr3;
    cout << endl;
}
发布了29 篇原创文章 · 获赞 11 · 访问量 3952

猜你喜欢

转载自blog.csdn.net/weixin_41462017/article/details/104736052