归并排序和快速排序的衍生问题(逆序对)

来源:互联网 发布:拓普康全站仪导出数据 编辑:程序博客网 时间:2024/06/06 09:26

  归并排序和快速排序存在一些衍生问题,比如计算一个数组中逆序对的数量。

  首先介绍逆序对的概念:从数组中随意选出一对数,若前者大于后者,则称为逆序对。而一个数组中的逆序对的数量可以衡量数组的有序程度,比如一个完全有序的数组1 2 3 4 5 6 7,其逆序对的数量为0,数组完全有序;而7 6 5 4 3 2 1中逆序对的数量达到最大值,所以数组的有序程度很低。

  对于求解逆序对数量的问题,可以采用暴力解法,其时间复杂度为O(n^2),代码如下:

template<typename T>int reserveOrder(T arr[], int n){    int num = 0;  //逆序对数量    for(int i = 0; i < n; i++)    {        for(int j = i + 1; j < n; j++)        {            if(arr[j] < arr[i])            {                ++num;            }        }    }    return num;}
  求解逆序对的另一个方法,可以使用归并排序来实现:



  在归并过程中,若当前 i 所指元素小于 j 所指元素的值,则 i 所指元素相对于 j 所指元素后面的所有元素都是顺序的;若当前 i 所指元素大于 j 所指元素的值,则 i 所指元素后面的所有元素(在该子序列2 3 6 8中)相对于 j 所指的元素都是逆序的,所以此时可以将逆序数量加4。利用归并排序来求解逆序对数量的优点在于,不需要一对一对的判断是否为逆序对,可以经过一次判断确定一个子序列中的所有元素都与另一个元素为逆序对。

  完整代码如下:

  SortTestHelper.h文件(包含辅助函数)

#include <iostream>#include <cstdlib>#include <ctime>  //clock()、CLOCKS_PER_SEC#include <cassert>  //包含函数assert()using namespace std;namespace SortTestHelper{    //辅助函数 - 随机产生一个数组    int* generateRandomArray(int n, int RangeL, int RangeR)  //返回数组首地址    {        //判断RangeL是否<=RangeR        assert(RangeL <= RangeR);  //参数为表达式,表达式为真时返回true,否则打印错误信息        int *arr = new int[n];        srand(time(0));        for(int i = 0; i < n ; i++)        {            arr[i] = rand() % (RangeR - RangeL + 1) + RangeL;  //使得产生的随机数在RangeL和RangeR之间        }        return arr;    }    //辅助函数 - 产生一个近乎有序的随机数组    int* generateNearlyOrderedArray(int n, int swapTime)    {        int *arr = new int[n];        for(int i = 0; i < n; i++)        {            arr[i] = i;  //先生成一个完全有序的数组        }        //然后交换几组元素,使之变成无序但近乎有序的数组        srand(time(0));        for(int j = 0; j < swapTime; j++)        {            //随机生成一个x位置和y位置            int posx = rand() % n;            int posy = rand() % n;            //交换x和y处的元素            swap(arr[posx], arr[posy]);        }        return arr;    }    //辅助数组 - 产生一个完全有序数组    int* generateTotallyOrderedArray(int n)    {        int *arr = new int[n];        for(int i = 0; i < n; i++)        {            arr[i] = i;        }        return arr;    }    //辅助函数 - 产生一个完全逆序数组    int* generateTotallyReserveOrderedArray(int n)    {        int *arr = new int[n];        for(int i = 0; i < n; i++)        {            arr[i] = n - i;        }        return arr;    }    //辅助函数 - 打印数组    template<typename T>    void printArray(T arr[], int n)    {        for(int i = 0; i < n; i++)        {            cout << arr[i] << " ";        }        cout << endl;    }    //辅助函数 - 判断数组是否有序(升序)    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)  //arr[]和n是函数指针需要的参数    {        clock_t starttime = clock();        sort(arr, n);  //调用函数sort()        clock_t endtime = clock();        //判断排序是否成功        assert(isSorted(arr, n));  //若是数组无序,则assert会自动调用abort()退出程序,不会执行下面的语句        cout << sortname << " needs " << double(endtime - starttime) / CLOCKS_PER_SEC << "s." << endl;    }    //辅助函数 - 测试算法的时间    template<typename T>    void testSort2(string name, long long(*algo)(T[], int), T arr[], int n)    {        clock_t start = clock();        algo(arr, n);        clock_t endtime = clock();        cout << name << " needs " << double(endtime - start) / CLOCKS_PER_SEC << "s." << endl;    }    //辅助函数 - 拷贝数组    int* copyIntArray(int a[], int n)    {        int *arr = new int[n];        //使用C++函数copy()        copy(a, a + n, arr);        return arr;    }}

  main.cpp文件(包含暴力求解算法和归并排序求逆序对数量)

#include <iostream>#include "SortTestHelper.h"using namespace std;//暴力解法求逆序对数量template<typename T>long long reserveOrder(T arr[], int n){    long long num = 0;    for(int i = 0; i < n; i++)    {        for(int j = i + 1; j < n; j++)        {            if(arr[j] < arr[i])            {                ++num;            }        }    }    return num;}//归并排序算法求逆序对数量static long long reserveNum = 0;template<typename T>void __merge(T arr[], int l, int mid, int r)  //[l...r](前闭后闭){    T aux[r - l + 1];    for(int i = l; i <= r; i++)  //i是aux的下标    {        aux[i - l] = arr[i];    }    //i、j是arr中的下标,k是arr中的下标    //i-l、j-l是aux中的下标    int i = l, j = mid + 1, k = l;    while(i <= mid && j <= r)    {        if(aux[i - l] <= aux[j - l])        {            arr[k++] = aux[i - l];            i++;        }        else        {            arr[k++] = aux[j - l];            j++;            reserveNum += mid - i + 1;        }    }    //出界条件    while(i <= mid)    {        arr[k++] = aux[i - l];        i++;    }    while(j <= r)    {        arr[k++] = aux[j - l];        j++;    }}template<typename T>void __mergeSort(T arr[], int l, int r){    if(l >= r)    {        return;    }    else    {        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);}int main(){    long n = 100000;    //测试 - 随机序列    int *a = SortTestHelper::generateRandomArray(n, 0, n);    int *a2 = SortTestHelper::copyIntArray(a, n);    cout << reserveOrder(a, n) << endl;    SortTestHelper::testSort2("reserveOrder", reserveOrder, a, n);    mergeSort(a2, n);    cout << reserveNum << endl;    SortTestHelper::testSort("mergeSort", mergeSort, a2, n);    delete[] a;    delete[] a2;}
  测试结果如下:


  若测试用例改为完全有序的序列:

int main(){        //测试 - 完全有序序列    int *b = SortTestHelper::generateTotallyOrderedArray(n);    int *b2 = SortTestHelper::copyIntArray(b, n);    cout << reserveOrder(b, n) << endl;    SortTestHelper::testSort2("reserveOrder", reserveOrder, b, n);    mergeSort(b2, n);    cout << reserveNum << endl;    SortTestHelper::testSort("mergeSort", mergeSort, b2, n);    delete[] b;    delete[] b2;}
  测试结果如下:


  若测试用例改为完全逆序的序列:

int main(){    //测试 - 完全逆序序列    int *c = SortTestHelper::generateTotallyReserveOrderedArray(n);    int *c2 = SortTestHelper::copyIntArray(c, n);    cout << reserveOrder(c, n) << endl;    SortTestHelper::testSort2("reserveOrder", reserveOrder, c, n);    mergeSort(c2, n);    cout << reserveNum << endl;    SortTestHelper::testSort("mergeSort", mergeSort, c2, n);    delete[] c;    delete[] c2;}
  测试结果如下: 


  从以上三种情况下可以看出,利用归并排序算法求解逆序对数量的时间性能非常理想,而利用暴力解法求得逆序对数量的时间性能是我们不能接受的。



  





原创粉丝点击