• 算法--排序基础


    基础的排序算法

    c++基础

    生成随即测试用例

     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;
        }
    

    生成近乎有序的测试用例

    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;
        }
    

    拷贝数组并返回新数组

     int *copyIntArray(int a[], int n){
    
            int *arr = new int[n];
            //* 在VS中, copy函数被认为是不安全的,此时可用for循环:)
            copy(a, a+n, arr);
            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(const 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;
        }
    

    选择排序

    template<typename T>
    void selectionSort(T arr[], int n){
    
        for(int i = 0 ; i < n ; i ++){
    
            int minIndex = i;
            for( int j = i + 1 ; j < n ; j ++ )
                if( arr[j] < arr[minIndex] )
                    minIndex = j;
    
            swap( arr[i] , arr[minIndex] );
        }
    }
    

    选择排序测试

    int main() {
    
        // 测试模板函数,传入整型数组
        int a[10] = {10,9,8,7,6,5,4,3,2,1};
        selectionSort( a , 10 );
        for( int i = 0 ; i < 10 ; i ++ )
            cout<<a[i]<<" ";
        cout<<endl;
    
        // 测试模板函数,传入浮点数数组
        float b[4] = {4.4,3.3,2.2,1.1};
        selectionSort(b,4);
        for( int i = 0 ; i < 4 ; i ++ )
            cout<<b[i]<<" ";
        cout<<endl;
    
        // 测试模板函数,传入字符串数组
        string c[4] = {"D","C","B","A"};
        selectionSort(c,4);
        for( int i = 0 ; i < 4 ; i ++ )
            cout<<c[i]<<" ";
        cout<<endl;
    
        return 0;
    }
    

    对选择排序进行他优化(在每一轮中, 可以同时找到当前未处理元素的最大值和最小值)

    template<typename T>
    void selectionSort(T arr[], int n){
    
        int left = 0, right = n - 1;
        while(left < right){
            int minIndex = left;
            int maxIndex = right;
    
            // 在每一轮查找时, 要保证arr[minIndex] <= arr[maxIndex]
            if(arr[minIndex] > arr[maxIndex])
                swap(arr[minIndex], arr[maxIndex]);
    
            for(int i = left + 1 ; i < right; i ++)
                if(arr[i] < arr[minIndex])
                    minIndex = i;
                else if(arr[i] > arr[maxIndex])
                    maxIndex = i;
    
            swap(arr[left], arr[minIndex]);
            swap(arr[right], arr[maxIndex]);
    
            left ++;
            right --;
        }
    
        return;
    }
    

    插入排序

    基本的插入排序(写法二看着更像是高手,嘻嘻嘻)

    template<typename T>
    void insertionSort(T arr[], int n){
    
        for( int i = 1 ; i < n ; i ++ ) {
    
            // 寻找元素arr[i]合适的插入位置
            // 写法1
    //        for( int j = i ; j > 0 ; j-- )
    //            if( arr[j] < arr[j-1] )
    //                swap( arr[j] , arr[j-1] );
    //            else
    //                break;
    
            // 写法2
            for( int j = i ; j > 0 && arr[j] < arr[j-1] ; j -- )
                swap( arr[j] , arr[j-1] );
    
        }
    
        return;
    }
    

    改进插入排序(将交换改为赋值)

    template<typename T>
    void insertionSort(T arr[], int n){
    
        for( int i = 1 ; i < n ; i ++ ) {
    
            T e = arr[i];
            int j; // j保存元素e应该插入的位置
            for (j = i; j > 0 && arr[j-1] > e; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    
        return;
    }
    

    冒泡排序

    template<typename T>
    void bubbleSort( T arr[] , int n){
    
        bool swapped;
    
        do{
            swapped = false;
            for( int i = 1 ; i < n ; i ++ )
                if( arr[i-1] > arr[i] ){
                    swap( arr[i-1] , arr[i] );
                    swapped = true;
    
                }
    
            // 优化, 每一趟Bubble Sort都将最大的元素放在了最后的位置
            // 所以下一次排序, 最后的元素可以不再考虑
            n --;
    
        }while(swapped);
    }
    

    冒泡排序的另一种优化

    template<typename T>
    void bubbleSort2( T arr[] , int n){
    
        int newn; // 使用newn进行优化
    
        do{
            newn = 0;
            for( int i = 1 ; i < n ; i ++ )
                if( arr[i-1] > arr[i] ){
                    swap( arr[i-1] , arr[i] );
    
                    // 记录最后一次的交换位置,在此之后的元素在下一轮扫描中均不考虑
                    newn = i;
                }
            n = newn;
        }while(newn > 0);
    }
    

    希尔排序

    在插入排序的基础上改变

    template<typename T>
    void shellSort(T arr[], int n){
    
        // 计算 increment sequence: 1, 4, 13, 40, 121, 364, 1093...
        int h = 1;
        while( h < n/3 )
            h = 3 * h + 1;
    
        while( h >= 1 ){
    
            // h-sort the array
            for( int i = h ; i < n ; i ++ ){
    
                // 对 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序
                T e = arr[i];
                int j;
                for( j = i ; j >= h && e < arr[j-h] ; j -= h )
                    arr[j] = arr[j-h];
                arr[j] = e;
            }
    
            h /= 3;
        }
    }
    

    比较SelectionSort, InsertionSort和BubbleSort和ShellSort四种排序算法的性能效率, ShellSort是这四种排序算法中性能最优的排序算法,以下是对20000个随机数的测试

    Selection Sort : 0.517969 s
    Insertion Sort : 0.267192 s
    Bubble Sort : 1.9947 s
    Shell Sort : 0.003899 s
    

    归并排序

    // 将arr[l...mid]和arr[mid+1...r]两部分进行归并
    template<typename  T>
    void __merge(T arr[], int l, int mid, int r){
    
        //* VS不支持动态长度数组, 即不能使用 T aux[r-l+1]的方式申请aux的空间
        //* 使用VS, 可以使用new的方式申请aux空间
        //* 使用new申请空间, 不要忘了在__merge函数的最后, delete掉申请的空间:)
        T aux[r-l+1];
        //T *aux = new T[r-l+1];
    
        for( int i = l ; i <= r; i ++ )
            aux[i-l] = arr[i];
    
        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){
    
            if( i > mid ){  // 如果左半部分元素已经全部处理完毕
                arr[k] = aux[j-l]; j ++;
            }
            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 ++;
            }
        }
    
        //delete[] aux;
    }
    
    // 递归使用归并排序,对arr[l...r]的范围进行排序
    template<typename T>
    void __mergeSort(T arr[], int l, int r){
    
        if( l >= r )
            return;
    
        int mid = (l+r)/2;
        __mergeSort(arr, l, mid);
        __mergeSort(arr, mid+1, r);
        __merge(arr, l, mid, r);
    }
    
    template<typename T>
    void mergeSort(T arr[], int n){
    
        __mergeSort( arr , 0 , n-1 );
    }
    

    比较InsertionSort和MergeSort两种排序算法的性能效率,整体而言, MergeSort的性能最优,Merge Sort是一个O(nlogn)复杂度的算法,可以在1秒之内轻松处理100万数量级的数据,注意:不要轻易尝试使用SelectionSort, InsertionSort或者BubbleSort处理100万级的数据,否则,你就见识了O(n^2)的算法和O(nlogn)算法的本质差异:)

    对于近乎有序的数组, 数组越有序, InsertionSort的时间性能越趋近于O(n),所以可以尝试, 当swapTimes(生成几乎有序数组时的参数,决定有序程度)比较大时, MergeSort更快,但是当swapTimes小到一定程度, InsertionSort变得比MergeSort快

    对上面的排序进行优化

    // 使用优化的归并排序算法, 对arr[l...r]的范围进行排序
    template<typename T>
    void __mergeSort2(T arr[], int l, int r){
    
        // 优化2: 对于小规模数组, 使用插入排序
        if( r - l <= 15 ){
            insertionSort(arr, l, r);
            return;
        }
    
        int mid = (l+r)/2;
        __mergeSort2(arr, l, mid);
        __mergeSort2(arr, mid+1, r);
    
        // 优化1: 对于arr[mid] <= arr[mid+1]的情况,不进行merge
        // 对于近乎有序的数组非常有效,但是对于一般情况,有一定的性能损失
        if( arr[mid] > arr[mid+1] )
            __merge(arr, l, mid, r);
    }
    
    template<typename T>
    void mergeSort2(T arr[], int n){
    
        __mergeSort2( arr , 0 , n-1 );
    }
    

    归并排序进一步种优化

    // 将arr[l...mid]和arr[mid+1...r]两部分进行归并
    // 其中aux为完成merge过程所需要的辅助空间
    template<typename  T>
    void __merge2(T arr[], T aux[], int l, int mid, int r){
    
        // 由于aux的大小和arr一样, 所以我们也不需要处理aux索引的偏移量
        // 进一步节省了计算量:)
        for( int i = l ; i <= r; i ++ )
            aux[i] = arr[i];
    
        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){
    
            if( i > mid ){  // 如果左半部分元素已经全部处理完毕
                arr[k] = aux[j]; j ++;
            }
            else if( j > r ){  // 如果右半部分元素已经全部处理完毕
                arr[k] = aux[i]; i ++;
            }
            else if( aux[i] < aux[j] ) {  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j]; j ++;
            }
        }
    
    }
    
    // 使用优化的归并排序算法, 对arr[l...r]的范围进行排序
    // 其中aux为完成merge过程所需要的辅助空间
    template<typename T>
    void __mergeSort2(T arr[], T aux[], int l, int r){
    
        // 对于小规模数组, 使用插入排序
        if( r - l <= 15 ){
            insertionSort(arr, l, r);
            return;
        }
    
        int mid = (l+r)/2;
        __mergeSort2(arr, aux, l, mid);
        __mergeSort2(arr, aux, mid+1, r);
    
        // 对于arr[mid] <= arr[mid+1]的情况,不进行merge
        // 对于近乎有序的数组非常有效,但是对于一般情况,有一定的性能损失
        if( arr[mid] > arr[mid+1] )
            __merge2(arr, aux, l, mid, r);
    }
    
    
    template<typename T>
    void mergeSort2(T arr[], int n){
    
        // 在 mergeSort2中, 我们一次性申请aux空间,
        // 并将这个辅助空间以参数形式传递给完成归并排序的各个子函数
        T *aux = new T[n];
    
        __mergeSort2( arr , aux, 0 , n-1 );
    
        delete[] aux;   // 使用C++, new出来的空间不要忘记释放掉:)
    }
    

    Merge Sort 2 只开辟了一次辅助空间, 之后将这个辅助空间以参数形式传递给完成归并排序的其他子函数,Merge Sort 2的性能优于 Merge Sort

    使用自底向上的归并排序算法

    template <typename T>
    void mergeSortBU(T arr[], int n){
    
        // Merge Sort Bottom Up 无优化版本
       for( int sz = 1; sz < n ; sz += sz )
            for( int i = 0 ; i < n - sz ; i += sz+sz )
             // 对 arr[i...i+sz-1] 和 arr[i+sz...i+2*sz-1] 进行归并
             __merge(arr, i, i+sz-1, min(i+sz+sz-1,n-1) );
    
    }
    

    进行优化

    template <typename T>
    void mergeSortBU(T arr[], int n){
    
        // Merge Sort Bottom Up 优化
        // 对于小数组, 使用插入排序优化
        for( int i = 0 ; i < n ; i += 16 )
            insertionSort(arr,i,min(i+15,n-1));
    
        for( int sz = 16; sz < n ; sz += sz )
            for( int i = 0 ; i < n - sz ; i += sz+sz )
                // 对于arr[mid] <= arr[mid+1]的情况,不进行merge
                if( arr[i+sz-1] > arr[i+sz] )
                    __merge(arr, i, i+sz-1, min(i+sz+sz-1,n-1) );
    
    }
    

    Merge Sort BU 也是一个O(nlogn)复杂度的算法,虽然只使用两重for循环,所以,Merge Sort BU也可以在1秒之内轻松处理100万数量级的数据,注意:不要轻易根据循环层数来判断算法的复杂度,Merge Sort BU就是一个反例

    比较Merge Sort和Merge Sort Bottom Up两种排序算法的性能效率,整体而言, 两种算法的效率是差不多的。但是如果进行仔细测试, 自底向上的归并排序会略胜一筹。

    总体来说, Merge Sort BU 比 Merge Sort 快一些。但优化后, 二者的性能差距不明显,

    快速排序

    // 对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){
    
        T v = arr[l];
    
        int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
        for( int i = l + 1 ; i <= r ; i ++ )
            if( arr[i] < v ){
                j ++;
                swap( arr[j] , arr[i] );
            }
    
        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){
    
        __quickSort(arr, 0, n-1);
    }
    

    比较Merge Sort和Quick Sort两种排序算法的性能效率,两种排序算法虽然都是O(nlogn)级别的, 但是Quick Sort算法有常数级的优势,Quick Sort要比Merge Sort快, 即使对Merge Sort进行了优化,是对于近乎有序的数组, 快速排序算法退化成了O(n^2)级别的算法,下面对其进行优化

    template <typename T>
    int _partition(T arr[], int l, int r){
    
        // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
        swap( arr[l] , arr[rand()%(r-l+1)+l] );
    
        T v = arr[l];
        int j = l;
        for( int i = l + 1 ; i <= r ; i ++ )
            if( arr[i] < v ){
                j ++;
                swap( arr[j] , arr[i] );
            }
    
        swap( arr[l] , arr[j]);
    
        return j;
    }
    
    // 对arr[l...r]部分进行快速排序
    template <typename T>
    void _quickSort(T arr[], int l, int r){
    
        // 对于小规模数组, 使用插入排序进行优化
        if( r - l <= 15 ){
            insertionSort(arr,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(n^2)级别的算法,

    双路快速排序

    // 双路快速排序的partition
    // 返回p, 使得arr[l...p-1] <= arr[p] ; arr[p+1...r] >= arr[p]
    // 双路快排处理的元素正好等于arr[p]的时候要注意,详见下面的注释:)
    template <typename T>
    int _partition2(T arr[], int l, int r){
    
        // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
        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 ){
            // 注意这里的边界, arr[i] < v, 不能是arr[i] <= v
            while( i <= r && arr[i] < v )
                i ++;
    
            // 注意这里的边界, arr[j] > v, 不能是arr[j] >= v
            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 _quickSort(T arr[], int l, int r){
    
        // 对于小规模数组, 使用插入排序进行优化
        if( r - l <= 15 ){
            insertionSort(arr,l,r);
            return;
        }
    
        // 调用双路快速排序的partition
        int p = _partition2(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);
    }
    

    双路快速排序算法也可以轻松处理近乎有序的数组,使用双快速排序后, 我们的快速排序算法可以轻松的处理包含大量元素的数组

    三路快速排序(可以对比单路快速排序)

    // 递归的三路快速排序算法
    template <typename T>
    void __quickSort3Ways(T arr[], int l, int r){
    
        // 对于小规模数组, 使用插入排序进行优化
        if( r - l <= 15 ){
            insertionSort(arr,l,r);
            return;
        }
    
        // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
        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]);
                i ++;
                lt ++;
            }
            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);
    }
    

    比较Merge Sort和双路快速排序和三路快排三种排序算法的性能效率,对于包含有大量重复数据的数组, 三路快排有巨大的优势,对于一般性的随机数组和近乎有序的数组, 三路快排的效率虽然不是最优的, 但是是在非常可以接受的范围里,因此, 在一些语言中, 三路快排是默认的语言库函数中使用的排序算法。比如Java:)

    逆序数对(归并排序)

    // 计算逆序数对的结果以long long返回
    // 对于一个大小为N的数组, 其最大的逆序数对个数为 N*(N-1)/2, 非常容易产生整型溢出
    
    // merge函数求出在arr[l...mid]和arr[mid+1...r]有序的基础上, arr[l...r]的逆序数对个数
    long long __merge( int arr[], int l, int mid, int r){
    
        int *aux = new int[r-l+1];
        for( int i = l ; i <= r ; i ++ )
            aux[i-l] = arr[i];
    
        // 初始化逆序数对个数 res = 0
        long long res = 0;
        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int j = l, k = mid + 1;
        for( int i = l ; i <= r ; i ++ ){
            if( j > mid ){ // 如果左半部分元素已经全部处理完毕
                arr[i] = aux[k-l];
                k ++;
            }
            else if( k > r ){ // 如果右半部分元素已经全部处理完毕
                arr[i] = aux[j-l];
                j ++;
            }
            else if( aux[j-l] <= aux[k-l] ){ // 左半部分所指元素 <= 右半部分所指元素
                arr[i] = aux[j-l];
                j ++;
            }
            else{ // 右半部分所指元素 < 左半部分所指元素
                arr[i] = aux[k-l];
                k ++;
                // 此时, 因为右半部分k所指的元素小
                // 这个元素和左半部分的所有未处理的元素都构成了逆序数对
                // 左半部分此时未处理的元素个数为 mid - j + 1
                res += (long long)(mid - j + 1);
            }
        }
    
        delete[] aux;
    
        return res;
    }
    
    // 求arr[l..r]范围的逆序数对个数
    // 思考: 归并排序的优化可否用于求逆序数对的算法? :)
    long long __inversionCount(int arr[], int l, int r){
    
        if( l >= r )
            return 0;
    
        int mid = l + (r-l)/2;
    
        // 求出 arr[l...mid] 范围的逆序数
        long long res1 = __inversionCount( arr, l, mid);
        // 求出 arr[mid+1...r] 范围的逆序数
        long long res2 = __inversionCount( arr, mid+1, r);
    
        return res1 + res2 + __merge( arr, l, mid, r);
    }
    
    // 递归求arr的逆序数对个数
    long long inversionCount(int arr[], int n){
    
        return __inversionCount(arr, 0, n-1);
    }
    

    寻找arr数组中第k小的元素(快速排序)

    main.cpp

    #include <iostream>
    #include <ctime>
    #include <cassert>
    #include <algorithm>
    #include "TestHelper.h"
    
    using namespace std;
    
    // partition 过程, 和快排的partition一样
    template <typename T>
    int __partition( T arr[], int l, int r ){
    
        int p = rand()%(r-l+1) + l;
        swap( arr[l] , arr[p] );
    
        int j = l; //[l+1...j] < p ; [lt+1..i) > p
        for( int i = l + 1 ; i <= r ; i ++ )
            if( arr[i] < arr[l] )
                swap(arr[i], arr[++j]);
    
        swap(arr[l], arr[j]);
    
        return j;
    }
    
    // 求出arr[l...r]范围里第k小的数
    template <typename T>
    T __selection( T arr[], int l, int r, int k ){
    
        if( l == r )
            return arr[l];
    
        // partition之后, arr[p]的正确位置就在索引p上
        int p = __partition( arr, l, r );
    
        if( k == p )    // 如果 k == p, 直接返回arr[p]
            return arr[p];
        else if( k < p )    // 如果 k < p, 只需要在arr[l...p-1]中找第k小元素即可
            return __selection( arr, l, p-1, k);
        else // 如果 k > p, 则需要在arr[p+1...r]中找第k-p-1小元素
             // 注意: 由于我们传入__selection的依然是arr, 而不是arr[p+1...r],
             //       所以传入的最后一个参数依然是k, 而不是k-p-1
            return __selection( arr, p+1, r, k );
    }
    
    // 寻找arr数组中第k小的元素
    // 注意: 在我们的算法中, k是从0开始索引的, 即最小的元素是第0小元素, 以此类推
    // 如果希望我们的算法中k的语意是从1开始的, 只需要在整个逻辑开始进行k--即可, 可以参考selection2
    template <typename T>
    T selection(T arr[], int n, int k) {
    
        assert( k >= 0 && k < n );
    
        srand(time(NULL));
        return __selection(arr, 0, n - 1, k);
    }
    
    // 寻找arr数组中第k小的元素, k从1开始索引, 即最小元素是第1小元素, 以此类推
    template <typename T>
    T selection2(T arr[], int n, int k) {
    
        return selection(arr, n, k - 1);
    }
    
    
    // 测试 selection算法
    int main() {
    
        // 生成一个大小为n, 包含0...n-1这n个元素的随机数组arr
        int n = 10000;
        int* arr = TestHelper::generateOrderedArray(n);
        TestHelper::shuffleArray(arr, n);
    
        // 验证selection算法, 对arr数组求第i小元素, 应该为i
        for( int i = 0 ; i < n ; i ++ ){
            assert( selection(arr, n, i) == i );
            cout<<"test "<<i<<" complete."<<endl;
        }
        cout<<"Test selection completed."<<endl;
    
        delete[] arr;
    
        cout << endl;
    
        // 验证selection2算法
        arr = TestHelper::generateOrderedArray(n);
        TestHelper::shuffleArray(arr, n);
    
        // 对arr数组求第i小元素, 应该为i - 1 (在selection2中, 第k小元素的k是从1索引的)
        for( int i = 1 ; i <= n ; i ++ ){
            assert( selection2(arr, n, i) == i - 1 );
            cout<<"test "<<i<<" complete."<<endl;
        }
        cout<<"Test selection2 completed."<<endl;
    
        delete[] arr;
    
        return 0;
    }
    

    TestHelper.h

    
    #ifndef OPTIONAL_3_SELECTION_TESTHELPER_H
    #define OPTIONAL_3_SELECTION_TESTHELPER_H
    
    #include <iostream>
    #include <algorithm>
    #include <ctime>
    
    using namespace std;
    
    namespace TestHelper {
    
        // 生成一个完全有序的数组
        int *generateOrderedArray(int n) {
    
            int *arr = new int[n];
            for (int i = 0; i < n; i++)
                arr[i] = i;
    
            return arr;
        }
    
        // 将数组arr随机化
        void shuffleArray(int arr[], int n){
    
            srand(time(NULL));
            for (int i = 0; i < n; i++) {
                int j = rand() % (n-i)+i;
                swap( arr[i], arr[j]);
            }
        }
    }
    #endif //OPTIONAL_3_SELECTION_TESTHELPER_H
    
    

    总结

    Merge Sort BU 比 Merge Sort 快一些。但优化后, 二者的性能差距不明显,

    Shell Sort虽然慢于高级的排序方式, 但仍然是非常有竞争力的一种排序算法,其所花费的时间完全在可以容忍的范围内, 远不像O(n^2)的排序算法, 在数据量较大的时候无法忍受,同时, Shell Sort实现简单, 只使用循环的方式解决排序问题, 不需要实现递归, 不占用系统占空间, 也不依赖随机数,所以, 如果算法实现所使用的环境不利于实现复杂的排序算法, 或者在项目工程的测试阶段, 完全可以暂时使用Shell Sort来进行排序任务:)

  • 相关阅读:
    替代谷歌、雅虎、必应的十大搜索引擎(1)
    CUDA Toolkit 4.1 RC1 新增LLVM编译器
    抢先报!GTC中国抢先报名,团购5折还送限量大礼
    Jacket for Matlab常见问题
    Jacket for Matlab 1.8新增功能介绍
    如何安装Jacket for Matlab
    Jacket for Matlab 2.1版本发布
    Matlab Jacket 2.0发布
    Attribute在.net编程中的应用(三)
    Attribute在.net编程中的应用(六)
  • 原文地址:https://www.cnblogs.com/ygjzs/p/12709160.html
Copyright © 2020-2023  润新知