借鉴自:算法专栏
目录
一、时间复杂度
1.算法效率
算法效率的评估:时间复杂度和空间复杂度
空间复杂度:执行程序所需要的存储空间,算法程序对计算机内存的使用情况
时间复杂度:不是表示一个程序解决问题具体需要花多少时间,而是当问题规模扩大后,程序需要的时间长度增长的有多快。衡量一个程序的好坏要看数据规模变大到数百倍后程序的运行时间是如何变化的。
设计算法是首先考虑系统环境,然后权衡时间复杂度和空间复杂度,寻找一个平衡点,而时间复杂度往往比空间复杂度更容易产生问题,因此时间复杂度是算法研发的主要部分。
2.时间复杂度
时间频度f(n):一个算法执行所需要的时间,理论上是不能计算出来的,必须上机测试才能知道,而算法中语句的执行次数与算法花费时间成正比,执行次数多花费时间多,因此通常通过计算算法中语句的执行次数来计算时间复杂度。算法中语句的执行次数即T(n),n是问题的规模,n不断变化时,T(n)也会不断变化,而时间复杂度就是这种变化呈现的规律。
时间复杂度的计算:
大O表示法,O(f(n))中f(n)的值可以是1,n,log(n),n^2等,因此将O(1),O(n),O(logn),O(n^2)分别称为常数阶、线性阶、对数阶、平方阶。
推导f(n)值的规则:
(1).用常数1代替运行次数中所有加法常数
(2).f(n)只保留最高阶项
(3).如果最高阶项存在且不是1,就去掉与这项相乘的常数
常数阶
int sum = 0,n = 100; //执行一次 sum = (1+n)*n/2; //执行一次 System.out.println (sum); //执行一次
上面例子中f(n)=3,根据规则一,此算法的时间复杂度为O(1)。如果sum = (1+n)*n/2这条语句再执行10遍,执行的10次与n的大小并没有关系,因此算法时间复杂度依然是O(1)。
线性阶
主要分析循环结构的运行情况
for(int i=0;i<n;i++){ //时间复杂度为O(1)的算法 ... }
上面算法循环体中的代码执行了n次,故时间复杂度为O(n)
对数阶
int number=1; while(number<n){ number=number*2; //时间复杂度为O(1)的算法 ... }
上面代码中,number每次乘2就会越来越接近n,当number大于等于n时就会退出循环。假设循环次数为x,则由2^x=n可得x=log n ,所以此算法的时间复杂度为O(log n)。
平方阶
下面代码是循环嵌套
for(int i=0;i<n;i++){ for(int j=0;j<n;i++){ //复杂度为O(1)的算法 ... } }
内部循环代码的时间复杂度为O(n),再经过外部循环n次,所以此算法的时间复杂度是O(n^2)。
再看下面的算法:
for(int i=0;i<n;i++){ for(int j=i;j<n;i++){ //复杂度为O(1)的算法 ... } }
注意内循环中变成了j=i,那么当i=0时,内循环执行了n次;i=1时,内循环执行了n-1次;i=2时,内循环执行了n-2次....以此类推,总共的循环执行次数为:
n+(n-1)+(n-2)+(n-3)+……+1
=(n+1)+[(n-1)+2]+[(n-2)+3]+[(n-3)+4]+……
=(n+1)+(n+1)+(n+1)+(n+1)+……
=(n+1)n/2
=n(n+1)/2
=n²/2+n/2根据规则2只保留最高阶项和规则3去掉常数项,所以此算法的时间复杂度为O(n^2)。
3.时间复杂度的比较
除了常数阶、线性阶、平方阶、对数阶,还有如下时间复杂度:
f(n)=nlogn时,时间复杂度为O(nlogn),可以称为nlogn阶。
f(n)=n³时,时间复杂度为O(n³),可以称为立方阶。
f(n)=2ⁿ时,时间复杂度为O(2ⁿ),可以称为指数阶。
f(n)=n!时,时间复杂度为O(n!),可以称为阶乘阶。
f(n)=(√n时,时间复杂度为O(√n),可以称为平方根阶。
时间复杂度为O(n)、O(logn)、O(√n )、O(nlogn )的算法,随着n的增加,复杂度提升不大,因此这些复杂度属于效率高的算法;反观O(2ⁿ)和O(n!)当n增加到50时,复杂度就突破十位数了,这种效率极差的复杂度最好不要出现在程序中,因此在动手编程时要评估所写算法的最坏情况的复杂度。
横坐标是 n,纵坐标是T(n),T(n)随着n的变化越小,算法效率就越高。
二、初等排序
1.插入排序
插入排序会将需要排序的数组分成两个部分,已排序部分和未排序部分。排序的规则即:将开头元素视为已排序部分,从未排序部分开始,将开头元素赋值给临时变量v,在已排序部分将所有比v值大的元素向后移