排序算法总结及C++实现

1 引入 认识时间复杂度

  常数时间的操作:一个操作如果和数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。

  时间复杂度为一个算法流程中,常数操作数量的指标。常用O (读作big O)来表示。具体来说,在常数操作数量的表达式中, 只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分 如果记为 f ( N ) f(N) f(N),那么时间复杂度为 O ( f ( N ) ) O(f(N)) O(f(N))

  评价一个算法流程的好坏,先看时间复杂度的指标,然后再分析不同数据样本下的实际运行时间,也就是常数项时间。

2 补充 对数器

  用来判定算法是否正确,笔试的时候不要裸奔去考,可以准备对数器,像是堆,数组,二叉树等的随机样本发生器,以快速检测出错的地方。

  对数器可以说是验证算法是否正确的一种方式。尤其是在笔试的时候,用贪心算法写出的程序,暂时无法用数学公式严格推导证明,只能通过大量的数据集验证算法的正确性。而大量的数据集当中要包括各种情况,各个方面都要考虑到,对我们自己来说,有时会考虑不周,而且又是在时间紧迫的情况下。所以对数器就派上了用场。

  1. 有一个你想测试的算法a【自己实现的排序算法】
  2. 实现一个绝对正确但复杂度高的算法b【使用#include中的sort函数】
  3. 实现一个随机样本产生器【下面实现中函数randomArrayGenerator】
  4. 实现比对算法a和b的方法,判断两个算法得出的结果是否相等【下面实现中函数isEqual】
  5. 多次(100000+)比对a和b来验证a是否正确【主函数中设置循环即可】
  6. 如果有样本出错,则打印出来分析
  7. 当对此对比测试都正确时,可以基本判断算法a正确

  其中要注意的几点:

  1. 要测试的算法a是时间复杂度比较低的算法,而算法b唯一要求就是保证正确,而不用管复杂度的高低
  2. 随机产生的样本大小要小,这里说的是样本的大小而不是样本的个数。因为出错时,小样本方便分析。
  3. 随机产生的样本个数要多,100000+ 只要大量随机产生的样本才可能覆盖所有的情况。
  4. 算法b也无法保证完全的正确,在不断出错调试的过程中,也可以不断完善b,最终达到a和b都正确的完美结果。
 /**
  随机生成随机个元素随机数字的数组
   **/
   vector<int> randomArrayGenerator(int maxSize,int maxValue)
   {
   
   
       default_random_engine e;
       uniform_real_distribution<double> u(0, 1); //随机数分布对象
       double random = u(e);
       int length = int((maxSize+1)*random*10);
       vector<int> res;
       for(int i=0;i<length;i++)
       {
   
   
           double randomval = u(e);
           res.push_back(int(randomval*(maxValue+1)));
       }
       return res;
   }
   /**
   比较两个数组是否相等
   **/
   bool isEqual(vector<int> arr1,vector<int> arr2)
   {
   
   
       if(arr1.size() != arr2.size())
           return false;
       else
       {
   
   
           for(int i=0;i<arr1.size();i++)
           {
   
   
               if(arr1[i] != arr2[i])
                   return false;
           }
       }
       return true;
   }
   /**
   复杂度很高,但是很简单的对比方法,用来对比所用方法的准确性
   可以使用#include<algorithm>中的sort函数
   **/

3 排序算法分析

关于排序的总结,这个网址的博主总结的很棒,而且设计了动画演示,对于理解有很大的帮助,以下仅作为我个人对排序算法的理解与总结,以及C++实现。

3.1 基于比较的排序

3.1.1 冒泡排序

  该算法称为冒泡排序是很形象的,质量较大的物体浮起来是需要更大的浮力的,所以每一次循环就会将最重【最大】的数沉下去,最终最轻【最小】的数浮在最上面。
  该算法在一次循环中,比较相邻的元素,如果前面的数大,就交换两个元素【最后的排序结果是从小到大】,因此每次循环只排好一个位置上的数,但是每次可以少排最后一个位置的数,因为已经排好了。
  时间复杂度 O ( N 2 ) O(N^2) O(N2),额外空间复杂度 O ( 1 ) O(1) O(1)

/**首先实现一个交换函数**/
void Swap(int *a,int *b)
   {
   
   
       int temp = *a;
       *a = *b;
       *b = temp;
   }
 /**【实现的时候出现的问题】
  一开始传入的是数组原型,在运行时会复制一个vector,然后交换操作也在复制的vector中进行,
  传出时并没有对原数组进行修改,所以测试结果时数组并没有排序。
  要想改变原数组,需要传入数组的引用,对数组进行修改
  **/
  void bubbleSort(vector<int> &arr)
  {
   
   
      if(arr.size() < 2)
          return;
      int length = arr.size();
      for (int e = length - 1; e > 0; e--) {
   
   
		for (int i = 0; i < e; i++) {
   
   
			if (arr[i] > arr[i + 1]) {
   
   
				Swap(&arr[i],&arr[i+1]);
			}
		}
	}
 }
3.1.2 选择排序

  选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法。
  【实际上,冒泡排序也是每次比较找到最大【小】的数放在最后,但是选择排序的效果应该是比冒泡排序好的,两者的差别在于每次比较数据大小之后是否进行交换,冒泡排序每次都进行交换,而选择排序则是记录最大值的索引,最后进行一次交换。】
  时间复杂度: O ( N 2 ) O(N^2) O(N2),额外空间复杂度 O ( 1 ) O(1) O(1)

void selectionSort(vector<int> &arr)
  {
   
   
       if(arr.size() < 2)
           return;
       int length = arr.size();
       int minIndex;
       for(int i=0;i<length;i++)
       {
   
   
           minIndex = i;
           for(int j=i+1;j<length;j++)
           {
   
   
               minIndex = (arr[j]<arr[minIndex])?j:minIndex;
           }
           Swap(&arr[i],&arr[minIndex]);
       }
   }
3.1.3 插入排序

  一个新的值插入到哪个位置比较合适。构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
时间复杂度最好情况: O ( N ) O(N) ON
     最差情况: O ( N 2 ) O(N^2) ON2与数据状况有关

/**
  每一次循环和后面一位比较大小,满足要求则交换,若交换再和前面的位进行比较。
   **/
   void insertSort(vector<int> &arr)
   {
   
   
       if(arr.size() < 2)
           return;
       int length = arr.size();
       for(int i=0;i<length;i++)
       {
   
   
           for(int j=i+1;j > 0;j--)
           {
   
   
               if(arr[j-1]>arr[j])
                   Swap(&arr[j-1],&arr[j]);
           }
       }
   }
3.1.4 归并排序

  归并排序之前首先看一下递归调用,课堂上学习到的都是递归就是自己调用自己,很玄乎。实际上递归函数中,是系统在帮忙压栈,当前函数跑到了第几行,以及当前所有的变量和信息都压入系统的栈中,然后继续跑下次调用函数的过程,如果不符合递归结束条件,继续压入栈中。【栈,先进后出,所以,递归函数是倒着跑回去。】

  任何递归行为都可以改成非递归。

  分析复杂度,估计递归行为的通式:master公式,子问题的样本来量需要是一样的才可以使用该公式
T ( n ) = a T ( n b ) + O ( n d ) T(n)=a T\left(\frac{n}{b}\right)+O\left(n^{d}\right) T(n)=aT(bn)+O(nd

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值