以插入排序为例子带你彻底理解算法中的时间复杂度和各种渐进符号
准备考研的过程中,复习数据结构和算法时先看的是严蔚敏老师的《数据结构》,这本书虽然好但是对时间复杂度和算法的渐进性讲解太少,读完之后对其依然没有深刻的理解,于是开始啃《算法导论》,不得不说这本书很难啃,但是确实啃清楚了算法的时间复杂度和渐进性。这篇博客我会用到初中程度的代数知识来帮助大家彻底理解。
我选择插入排序作为例子因为这个算法的思路足够简单,可以很容易的分析出最好情况和最坏情况。我们先来看一下插入排序的伪代码,我使用C++来书写代码,有注释:
//插入排序的算法思路很简单,联想打扑克牌时理顺手中牌的时候,你从左往右依次检查你的牌,并将其和前面的牌进行比较,然后将其插入正确的位置 void insertion_sort(int arr[],int length){ int key;//key作为当前你所检查的这张牌,为了方便表达我们称之为 关键牌 for(int i=1;i<length;i++){//从第二张牌开始,一直迭代到最后一张牌 key = arr[i];//将本次需要排序的牌拿出,作为关键牌 int j = i-1;//从关键牌的前一张开始往前检查,对比大小 //当被对比的牌比关键牌大时,关键牌前面所有的牌都比关键牌大时,把牌往后挪动一个位置 while(j>-1 && arr[j] > key){ arr[j+1] = arr[j]; j--; } arr[j+1] = key;//将关键插入正确的位置 } }
那么我们该如何知道,这段代码在实际执行时所花费的时间呢?当然可以上机测试,但是那样是经验论者和低端玩家干的事情,这里我们需要一些简单的数学知识和逻辑分析就能算出运行时间,我们假设,这次共排序一个长度为n的数组,那么每句代码执行所需要的时间代价和执行次数可以用下表表示:
代码 时间代价 执行次数
int key c1 1次
int i=1;i<length;i++ c2 n次
key = arr[i] c3 n-1次
int j = i-1 c4 n-1次
while(j>-1 && arr[j] > key) c5 这里我们无法判断具体的执行次数,但是知道外层循环要执行n-1次,假设第i次循环中这里执行k(i)次,那么共执行k(1)+k(2)+...+k(n-1)次
arr[j+1] = arr[j] c6 循环内的语句总比循环数少1,于是共执行(k(1)-1)+(k(2)-1)+...+(k(n-1)-1)次
j--; c7 同理,共执行(k(1)-1)+(k(2)-1)+...+(k(n-1)-1)次
arr[j+1] = key c8 n-1次