知识储备:
1、常数操作
固定时间的操作,比如任意一个int类型数字在java里都是一个32位二进制数字,任意两个int型数字的加减乘除所消耗的时间是一样的,底层都是操作的32位的二进制数字。
2、常见的常数操作
- 常见的算数运算符(加、减、乘、除、求余等)
- 常见的位运算(>>、>>>、<<、|、&、^等)
- 赋值、比较、自增、自减等
- 数组寻址操作
什么是时间复杂度
时间复杂度是判定一个算法快慢性能的指标,只是代表算法运算快慢的复杂性,并不表示运行的时间。一个算法执行一次常数操作就是一个时间复杂度,记作O(1),通常叫做bigO1。比如设计一个算法遍历长度为N的数组,对每个位置的数字进行加1,那么该算法的复杂度是多少呢?
code1
package net.csdn.test;
import java.util.Arrays;
public class Test1 {
public static void main(String[] args) {
//测试举例是一个长度为5的数组,题目是长度为N(N>1)的数组
int arr[]= {1,3,5,7,9};
everyItemAddOne(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 对数组的每个元素加1
* @param arr
*/
public static void everyItemAddOne(int arr[]) {
for(int i=0;i<arr.length;i++) {
arr[i]+=1;
}
}
}
我们主要关注everyItemAddOne这个算法,首先我们看一下这个算法的流程是怎么样的。 我们知道对于常规for循环操作,大概分三步,例如这个算法中,是从0位置开始算:
1、申请一个变量i,设定为0
2、判断该变量i是否小于一个值(一般小于数组的长度)
3、进行算法逻辑操作,例如该算法操作就是将数组每一位数字加1
4、对变量i进行加1
在上述流程中步骤1是只执行一次的,步骤2是要执行(N+1)次,其余的都会执行N次,所以时间复杂度应该是O(1)+O(N)+O(1)+O(N)+O(N)=3(N)+2O(1),但是我们经常却说该算法的时间复杂度是O(N),这是因为时间复杂度的推导满足三大法则:
- 用常数1取代运行时间中的所有加法常数
- 只保留最高阶项
- 去除最高阶的常数
根据第一条,2O(1)是一个常数项,该算法时间复杂度的最高阶是1阶,通过第2、3条规则可知,最终时间复杂度是O(N)。
下面我们通过三个经典排序算法,再清楚的认识以下时间复杂度的概念
选择排序
流程:
假设有一个长度为N的数组(N>=2),依次进行如下流程
1、首先遍历长度为N的数组,找到数组的最小值,与0位置互换
2、接着遍历余下的N-1长度数组,找到最小值,与1位置互换
2、接着遍历余下的N-2长度数组,找到最小值,与2位置互换
… … … …
3、直至剩最后1个长度的数组,就是最大值
了解完这个流程直接开撸代码:
代码:
code2
package net.csdn.test;
public class TestSelectSortCode {
public static void SelectSort(int arr[]) {
if(arr==null||arr.length<2) {
return;
}
//0~N-1 上选择最小值,放到0位置
//1~N-1上选择最小值,放到1位置
//2~N-1上选择最小值,放到2位置
for(int i=0;i<arr.length-1;i++) {
int minIndexNum=i;
for(int j=i+1;j<arr.length;j++) {
//从i~N-1上找到最小值下标
minIndexNum=arr[j]<arr[minIndexNum]?j:minIndexNum;
}
swap(arr, i, minIndexNum);
}
}
public static void swap(int[] arr,int i,int j) {
int tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
}
算法分析:
之前说过,时间复杂度分析是建立在常数操作的基础上的,这个算法里常数操作都有哪一些呢?
1、判断数组是否为空和长度是否小于2是常数操作,且只执行一次
if(arr==null||arr.length<2) {
return;
}
2、之后就是两个嵌套循环,根据code1知道第一个for循环执行下来时间复杂度应该是O(N),那第二个for循环执行下来也是O(N)么?当然不是,因为当外层循环执行第一次时,内层for循环要执行N-1次;因为当外层循环执行第二次时,内层for循环要执行N-2次;因为当外层循环执行第三次时,内层for循环要执行N-3次,以此类推内层for循环的时间复杂度应该是(N-1)+(N-2)+(N-3)+…+1,根据等差队列公式,并且时间复杂度与常数无关只关心最高阶,所以最终复杂度应该是O(N2)。
3、最后就是内层循环的交换函数swap,我们知道,每调用一次该函数,应该有三次常数操作,内层循环的时间复杂度是3O(N2),也是O(N2)
4、所以最终时间复杂度是O(1)+O(N)+O(N2)+O(N2)=2O(N2)+O(N)+O(1),根据时间复杂度三大法则,时间复杂度应该是O(N2)
注意:对于步骤1产生的时间复杂度,之后计算的时候都将忽略,只分析对时间复杂度有影响的部分
冒泡排序
流程:
假设有一个长度为N的数组(N>=2),依次进行如下流程
1、在0~N-1范围上依次对相邻的两个数字进行比较,谁大谁往右
2、在0~N-2范围上依次对相邻的两个数字进行比较,谁大谁往右
3、在0~N-3范围上依次对相邻的两个数字进行比较,谁大谁往右
… … … …
4、最后位置0和1位置比较,谁大谁往右
冒泡排序的意思是依次找打最大值,并把最大值排到最右边
代码:
code3
package net.csdn.test;
public class TestMaoPaoSortCode {
public static void maoPaoSort(int arr[]) {
if(arr==null||arr.length<2) {
return;
}
for(int i=0;i<arr.length-1;i++) {
for(int j=0;j<arr.length-(i+1);j++) {
if(arr[j]>arr[j+1]) {
swap(arr, j, j+1);
}
}
}
}
public static void swap(int[] arr,int i,int j) {
int tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
}
算法分析:
1、这也是两个嵌套for循环,第一层时间复杂度是O(N)
2、我们看一下第二个嵌套的for循环,当外层循环执行第一次时,内层for循环要执行N-1次;因为当外层循环执行第二次时,内层for循环要执行N-2次;因为当外层循环执行第三次时,内层for循环要执行N-3次,以此类推内层for循环的时间复杂度应该是(N-1)+(N-2)+(N-3)+…+1,发现这时间复杂度应该是O(N2)
3、嵌套for循环里面的数据交换函数,每执行依次时间复杂度为3O(1),所有总的时间复杂度是3O(N2)
4、对时间复杂度求和O(N)+O(N2)+3O(N2)=4O(N2)+O(N),根据时间复杂度三大法则,时间复杂度应该是O(N2)
插入排序
流程:
假设有一个长度为N的数组(N>=2),依次进行如下流程
1、首先做到位置0~1上有序,那么如果位置1比位置0小,就交换,否则就停止
2、接着做到位置0~2上有序,那么如果位置2比位置1小,就交换,否则就停止;如果位置1比位置0小,就交换,否则就停止
3、接着做到位置0~3上有序,那么如果位置3比位置2小,就交换,否则就停止;如果位置2比位置1小,就交换,否则就停止;如果位置1比位置0小,就交换,否则就停止
4、接着做到位置0~4上有序,那么如果位置4比位置3小,就交换,否则就停止;那么如果位置3比位置2小,就交换,否则就停止;如果位置2比位置1小,就交换,否则就停止;如果位置1比位置0小,就交换,否则就停止
… … … …
5、最后做到位置0~4上有序,那么如果位置N比位置N-1小,就交换,否则就停止;如果位置N-1比位置N-2小,就交换,否则就停止;… …如果位置3比位置2小,就交换,否则就停止;那么如果位置2比位置1小,就交换,否则就停止;如果位置1比位置0小,就交换,否则就停止
这就好比我们码手里的牌,再次取牌前,手里的牌肯定是有序的,然后我们依次从左边和取到的新牌比较大小,直至找到合适地方
代码:
public static void insertSort(int arr[]) {
if(arr==null||arr.length<2) {
return;
}
for(int i=1;i<arr.length;i++) {
for(int j=i-1;j>=0&&arr[j]>arr[j+1];j--) {
swap(arr, j, j+1);
}
}
}
public static void swap(int[] arr,int i,int j) {
int tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
算法分析:
假设有一个长度为N的数组(N>=2),依次进行如下流程
1、这也是两个嵌套for循环,第一层时间复杂度是O(N)
2、假如i=1时,第二层for循环的时间复杂度就是O(1)
假如i=2时,第二层for循环的时间复杂度就是O(2)
假如i=3时,第二层for循环的时间复杂度就是O(3)
… … … …
假如i=N-1时,第二层for循环的时间复杂度就是O(N-1)
所以二层嵌套for循环的时间复杂度也是O(N)
3、嵌套for循环里面的数据交换函数,每执行依次时间复杂度为3O(1),所有总的时间复杂度是3O(N)
4、对时间复杂度求和O(N)+O(N)+3O(N)=4O(N)+O(N)=5O(N),根据时间复杂度三大法则,时间复杂度应该是O(N)
什么是额外空间复杂度
定义:除了算法给定的空间,为了得到算法结果,需要额外申请的空间叫做额外空间复杂度。额外空间复杂度是判断算法占用空间大小的指标
示例
比如给定一个长度为N的数组,统计每一种数字出现的次数,我们常常的做法是初始化一个Map,那么这个Map的长度就是额外空间复杂度,考虑最坏的一种情况,如果数组的数字都不相同,那么Map的长度就会和给定数的组长度一样,所以这中解法的额外空间复杂度就是O(N)