时间复杂度
1、时间频度
时间复杂度通常是衡量算法的优劣的,衡量算法的时间严格来讲是很难衡量的,由于不同的机器性能不用环境都会造成不同的执行时间。算法的执行时间和语句的执行次数成正比,因此通过计算执行测试来推断执行时间。算法中语句执行次数称为语句频度或时间频度,记为T(n),n是问题的规模,T是Time,即时间频度。
2、时间复杂度
n不断变化时,T(n)也在不断变化,为了考察两者变化时呈现什么规律,可以使用时间复杂度来计算。
通常操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),存在一个正常数c使得f(n) * c >= T(n)恒成立。记作T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。
简言之,只要计算一下语句的执行次数和n之间的关就可以了,去除系数部分,低阶项目部分,就是时间复杂度的值了。
3、 时间复杂度的计算
3.1 给定任意长度数组,读取数组第一个元素的值
无论数组长度n为多少,代码执行一次。因此,时间复杂度为O(1)。
//无论数组的长度n为多少,
public void firstEle(int[] arr){
return arr[0] ; //代码只执行一次。
}
3.2 循环n次,输出hello world
随着n的增大,打印函数逐渐增多。打印代码执行的次数是n,因此时间复杂度为O(n)。
for(int i = 0 ; i < n ; i ++){
//执行一次。
System.out.println("hello world") ;
}
3.3 打印99乘法表
打印99正方乘法表,打印语句输出9 * 9 = 81次。
打印语句执行次数:$$n * n = n2$$,因此时间复杂度是$$O(n2)$$
for(int i = 1 ; i <= n ; i ++){
for(int j = 1 ; j <= n ; j ++){
//打印语句的执行次数n * n次。
System.out.print(j + " x " + i + "=" + (i * j));
}
}
3.4 打印正三角的99乘法表
正三角形状的99表格打印语句的执行次数为:
去掉系数部分和低阶部分,时间复杂度为仍然为$$O(n^2)$$
for(int i = 1 ; i <= n ; i ++){
for(int j = 1 ; j <= i ; j ++){
//打印语句的执行次数n * n次。
System.out.print(j + " x " + i + "=" + (i * j));
}
}
3.5 冒泡排序
冒泡排序是经典的排序算法是每次比较相邻的两个元素进行对调,进行多轮,直到数列有序。
对于5个元素的冒泡排序来说,共需要比较4 + 3 + 2 + 1次比较。因此时间复杂度为:
因此时间复杂度为:$$O(n^2)$$,这是冒泡排序最坏的情况下的时间复杂度。
int[] arr = ... ;
//外层比较n-1次
for(int i = 0 ; i < arr.length - 1 ; i ++){
//比较n-1-i次
for(int j = 0 ; j < arr.length -1 - i ; j ++){
int temp = arr[j] ;
if(arr[j] > arr[j + 1]){
arr[j] = arr[j + 1] ;
arr[j + 1] = temp ;
}
}
}
冒泡排序可以进行优化,在最好的情况下,复杂度为O(n),思路如下:
设置标志位flag=1,表示整个数列已经有序,就不需要再排序了,在里层循环中,如果发现没有进行过数据交换,就说明数列已经有序。
在最好情况下,就是数列本身已经有序,只需要执行外层的n-1次循环即可,因此复杂度为O(n)。
int[] arr = ... ;
//外层比较n-1次
for(int i = 0 ; i < arr.length - 1 ; i ++){
boolean flag = true ;
//比较n-1-i次
for(int j = 0 ; j < arr.length -1 - i ; j ++){
int temp = arr[j] ;
if(arr[j] > arr[j + 1]){
//发生交换,就设置标志位为false,即数列无序。
flag = false ;
arr[j] = arr[j + 1] ;
arr[j + 1] = temp ;
}
}
//判断如果是数列有序,则终止循环。
if(flag){
break ;
}
}
3.6 折半查找
折半查找也叫二分查找,是高效的查找方式,前提条件是数列需要时有序数列。图例如下:
代码如下:
//有序数组
int[] arr = ... ;
int min = arr[0] ;
int max = arr[0] ;
int mid = arr[arr.length/2] ;
//目标元素
int targ = 3 ;
for(int min = 0 , max = arr.length - 1 ; min <= max ; ){
int mid = (min + max) / 2 ;
//命中了
if(arr[mid] == targ){
return mid ;
}
//落在右侧范围
else if(arr[mid] < targ){
min = mid + 1 ;
}
//落在左侧范围
else{
max = mid - 1 ;
}
}
折半查找的最坏时间复杂度为:以2为底,n的对数。
3.7 hashmap
哈希map是java中最重要的集合之一,设计非常巧妙,使用通过数组+链表方式组合实现,哈希的目的是让对象在空间内尽可能分散。那么HashMap的时间复杂度是多少呢?
如果hashmap的每个桶内的元素个数最多不会超过一个常数C,当增加元素时,不断增加桶的数量,而保证每个桶内的元素量固定,因此就可以保证最快的查询速度,则时间复杂度就是$$O(C*n^2)$$,去掉系数部分就是
如果在最坏的情况下就是所有元素进入同一个桶,导致给桶内的链条非常长,则检索数据时,时间复杂度为:
3.8 矩阵的乘积
矩阵我们按照阶数为n的两个方阵进行计算,矩阵的乘法运算法则为:
例如:
方阵乘法的代码如下:
//方阵使用二位数组表示
int[][] a = ... ;
int[][] b = ... ;
//结果矩阵
int[][] r = ... ;
//[思路]
//从结果矩阵出发,结果矩阵也是一个阶数为n的方阵,但是每个对应的元素
//循环n次计算累加和得到。
//循环行数
for(int i = 0 ; i < a.length ; i ++){
//循环列的个数,这里仍然是m.length是因为方阵
for(int j = 0 ; j < b.length ; j ++){
for(int k = 0 ; k < a.length ; k ++){
r[i][j] = r[i][j] + a[i][k] * b[k][j] ;
}
}
}
方阵的矩阵乘法的计算次数为$$n3$$,因此时间复杂度为$$O(n3)$$。