数据结构精讲1-认识时间复杂度和额外空间复杂度

知识储备:
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)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卧龙在天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值