探究O(n^2)和O(nlogn)时间复杂度下各排序算法应用场景(一)

O(n^2)时间复杂度下各排序算法效率比较

  最近学习了排序算法,有了一些浅显的理解,文章参考了刘宇波老师的讲解,Mark下来分享给大家,也算记录下本阶段的学习成果,望各位大牛多多指教。
  首先是为什么要用算法,顾名思义算法是优化复杂计算的方法,可以对比下nlogn和n^2级别算法间的差异。


                   nlogn和n^2复杂度算法效率对比
这里写图片描述

  显而易见,在基数n较小的情况下,差异无从体现。但当数量级达到一定数量后,效果的差别就极其巨大了,这就好比LOL开局你刚买个树叉对面已然6神装一样的差距,这还打毛?GG好吧。同理,在目前的大数据时代非常有必要对算法进行一些有效的利用和优化,不然,拿什么和对手闹?
  回归正题,既然nlogn的效率更高,那为何我们还需要n^级别的算法呢?在于以下几点:
  1. 易于实现,编码简单。
  2. 一些特殊情况下,简单排序算法更有效。
  3. 作为子过程,改进更复杂的排序算法。
  扯了半天,现在进入正题: O(n^2) 级别排序算法

  主要包括以下几种:
   1.选择排序  2.插入排序   3.冒泡排序  4.希尔排序
  这里只实现前三种方法进行对比,希尔排序请各位自行尝试。
  首先我们需要搭建基本的测试环境(封装测试函数)用以后面的效率对比。
  说明:这里我们假定一个名词:随机复杂度
     高->相对顺序程度低(如165423) 低->相对顺序程度高(如123465)

namespace SortTestHelper {
    //生成有n个元素的随机数组,每个元素的随机范围为[rangeL,rangeR]
    int* generateRandomArray(int n, int rangeL, int rangeR)
    {
        assert(rangeL <= rangeR);
        int *arr = new int[n];
        srand(time(NULL));  //ctime
        for (int i = 0; i < n; i++)
            arr[i] = rand() % (rangeR - rangeL + 1) + rangeL;
        return arr;
    }

    //生成有n个元素的随机数组,且近乎有序,n:元素个数,swapTimes:交换次数
    int * generateRandomNearArr(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 randomX = rand() % n;
            int randomY = rand() % n;
            swap(arr[randomX], arr[randomY]);
        }
        return arr;
    }

    //复制整形数组(用于测试多个算法效率)
    int * copyArr(int a[], int n)
    {
        int * arr = new int[n];
        copy(a, a + n, arr);        //a是头指针,a+n是尾指针
        return arr;
    }

    //检测排序是否成功
    template<typename T>
    bool isSort(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 name, void(*sort)(T[], int), T arr[], int n)
    {
        clock_t startTime = clock();
        sort(arr, n);
        clock_t endTime = clock();
        assert(isSort(arr, n));
        cout << name << " efficiency:" << double(endTime - startTime) / CLOCKS_PER_SEC <<" s"<< endl;
        return;
    }
}

1 ) 选择排序

  原理:简单举例,一组数 12345876 需要升序排列,O(n^2)时间复杂度可以理解为需要两次for循环(即每个数需要操作n次,n个数即n^2),第一个for 循环遍历所有数,第二个for循环遍历对比得到当前未排序数中的最小值,将最小值交换到当前位置(本例为最左边未排序位置),这样每次循环都将未排序数中的最小值放到左边,依次遍历即可完成选择排序,代码如下:

//选择排序
template <typename T>
void selectionSort(T arr[], int n)
{
    for (int i = 0; i < n; ++i)
    {
        //寻找[i,n)区间里的最小值
        int minIndex = i;   //最小值下标
        for (int j = i + 1; j < n; ++j)
            if (arr[j]<arr[minIndex])
                minIndex = j;   //始终指向最小值
        swap(arr[i], arr[minIndex]);
    }
}

2)插入排序

  原理:依然两层for循环,第一层循环遍历所有数,第二层循环对比当前值(arr[j])和前值(arr[j-1])的大小,如arr[j]

//插入排序(未优化)
template <typename T>
void insertSort(T arr[], int n)
{
    for (int i = 1; i < n; ++i)
    {
        for (int j = i; j > 0&& arr[j] < arr[j - 1]; --j)    //二层循环可以提前结束,
                swap(arr[j], arr[j - 1]);
    }
    return;
}

  由于二层循环可以提前终止(arr[j]>arr[j-1]),所以理论上插入排序是要优于选择排序的,但实际测试结果并不理想,插入排序反而慢于选择排序。

这里写图片描述

  主因:我们处理插入排序时使用了swap函数,实际上交换操作所消耗的时间要远远大于比较,swap相当于进行了3次赋值,这使得插入排序并没有达到其应有的效率,需要进一步优化。
  优化:将3次赋值变成一次赋值,请大家参照代码自行脑补~

//插入排序(优化)
template <typename T>
void insertSort(T arr[], int n)
{
    for (int i = 1; i < n; ++i)
    {
        //寻找元素arr[i]合适的插入位置
        T e = arr[i];
        //j保存元素e应该插入的位置
        int j;
        for (j = i; j > 0 && arr[j - 1]>e; --j) //二层循环可以提前结束,
        {
            arr[j] = arr[j - 1];
        }
        arr[j] = e;
    }
}

这里写图片描述

再次对比优化后的效率提高显著。
之后我们再对随机复杂度低的数组排序对比:

这里写图片描述

  发现插入排序速度优势非常明显,可以想见,在一些场景下(如系统日志,个别地方出现错乱但整体有序),当排序数组近乎有序时,插入排序将升级成O(n)级别的排序算法,具体可以在复杂算法中做为一个子方法进行优化,这也是O(n^2)级别算法存在的价值。


3 )冒泡排序

  原理:冒泡排序实现起来比较简单,也是入门最常见的一种算法,在此不过多赘述,就是把最大(小)值每轮循环冒泡到最右(左)边,依次循环完成排序,由于其交换次数较多,所以在随机复杂度高的情况下效率比选择排序还低,在大数据量下的使用价值不是很高(也可优化)。发现插入排序速度优势非常明显,可以想见,在一些场景下(如系统日志,个别地方出现错乱但整体有序),当排序数组近乎有序时,插入排序将升级成O(n)级别的排序算法,具体可以在复杂算法中做为一个子方法进行优化,这也是O(n^2)级别算法存在的价值。

template <typename T>
void BubbleSort(T arr, int n)
{
    //第一次循环表示循环次数
    for (int i = 1; i < n; i++)
    {
        //第二次循环将最大值冒泡到右边
        for (int j = 0; j < n - i; j++)
        {
            if (arr[j] > arr[j + 1])
                swap(arr[j], arr[j + 1]);
        }
    }
}

三种O(n^2)算法效率对比:

随机复杂度高:

  
这里写图片描述

随机复杂度低:

这里写图片描述
  
  可见,这三种排序方法中插入排序综合性能最优,且在随机复杂度低的情况下效率可以接近O(n),其它两种排序方法在效率上不占优,但能提供更多思路上的延伸以完成更加复杂的算法。
  这样我们就简单的完成了O(n^2)级别排序算法的对比,下章我们将继续进行 nlog(n) 级别的排序算法比较。

备注:
测试机器配置: Core i7 3610QM / WIN10专业版 / VS2015
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值