数据结构基础之《(1)—复杂度》

一、评估算法优劣的核心指标是什么?

1、时间复杂度(流程决定)
2、额外空间复杂度(流程决定)
3、常数项时间(实现细节决定)

二、常数操作

1、什么是常数时间的操作?
如果一个操作的执行不以具体样本量为转移,每次执行时间都是固定时间。称这样的操作为常数时间的操作。
比如:执行一个数组的寻址,执行的时间和数据量没关系,第1000个位置和第1000万个位置,时间是一样的,计算机用偏移量获得。

2、常见的常数时间的操作
(1)常见的算术运算(+、-、*、/、%等)
(2)常见的位运算(>>、>>>、<<、|、&、^等)
>>是带符号右移,>>>是不带符号右移
(3)赋值、比较、自增、自减操作等
(4)数组寻址操作
总之,执行时间固定的操作都是常数时间的操作。
反之,执行时间不固定的操作,跟样本量有关的,都不是常数时间的操作。

3、什么不是常数时间操作
LinkedList,底层是一个双向列表。
要在list里面get一个数据,它不会像数组寻址一样,因为它不是连续区间。它是从0位置往下数。

4、时间复杂度就是来衡量流程中发生了多少次常数操作这件事
举例:选择排序
(1)先在0到n-1中,找到最小值位置在哪儿,然后把最小值和0位置的数交换
(2)0位置的数搞定了
(3)在1到n-1中,找到最小值的位置在哪儿,然后把最小值和1位置的数交换
(4)然后重复2到n-1,3到n-1。。。一直到n-1到n-1

这个流程发生了多少次常数操作?
步骤:
(1)单次:看+比
(2)一次换

n * (看+比) + 1
(n-1) * (看+比) + 1
(n-2) * (看+比) + 1
...

看是1次,比也是1次

总的表达式变为:
n * (2) + 1
(n-1) * (2) + 1
(n-2) * (2) + 1
...

= 2 * (n + n-1 + n-2 + ... + 1) + n

等差数列可以化简为(n(1+n)/2把系数换成a、b加个常数c)
= 2an² + bn + n + c
a、b、c是常数

最终的时间复杂度是抹掉常数项、高阶项系数和低阶项,最终得到n²(n的平方)

三、如何确定算法流程的总操作数量与样本数量之间的表达式关系?

1、想象该算法流程所处理的数据状况,要按照最差情况来。
2、把整个流程彻底拆分为一个个基本动作,保证每个动作都是常数时间操作。
3、如果数据量为N,看基本动作的数量和N是什么关系。

四、如何确定算法流程的时间复杂度?

当完成了表达式的建立,只要把最高阶项留下即可。低阶项都去掉,高阶项的系数也去掉。
记为:O(忽略掉系数的高阶项)

当样本量大到足够大的时候,决定算法快慢的就是高阶项的东西。

五、时间复杂度的意义

1、抹掉了好多东西,只剩下了一个最高阶项,那这个东西有什么意义呢?
2、时间复杂度的意义在于:
当我们要处理的样本量很大很大时,我们会发现低阶项是什么不是最重要的;每一项的系数是什么,不是最重要的。真正重要的就是最高阶项是什么。
3、这就是时间复杂度的意义,它是衡量算法流程的复杂度的一种指标,该指标只与数据量有关,与过程之外的优化无关。

六、实践时间复杂度的估算

1、选择排序
分析:见上面

	public static void selectionSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		// 0~n-1
		// 1~n-1
		// 2~n-1
		for (int i=0; i<arr.length-1; i++) { // i~n-1
			// 最小值在哪个位置上i~n-1
			int minIndex = i;
			for (int j=i+1; j<arr.length; j++) { // i~n-1上找最小值的下标
				minIndex = arr[j] < arr[minIndex] ? j : minIndex;
			}
			swap(arr, i, minIndex);
		}
	}
	
	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}

2、冒泡排序
分析:
数组:5 4 6 3 2 1
(1)0到5位置,0位置和1位置比谁大,交换到后面,1位置和2位置比谁大,交换到后面。。。
(2)最大值一定是到最后了
(3)在0到4位置上重复这件事
也是等差数列,复杂度为O(n²)

	public static void bubbleSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		//0~n-1
		//0~n-2
		//0~n-3
		//轮数
		for (int e=arr.length-1; e>0; e--) { //0~e
			//每轮需要交换的次数
			for (int i=0; i<e; i++) {
				if (arr[i] > arr[i+1]) {
					swap(arr, i, i+1);
				}
			}
		}
	}
	
	//交换arr的i和j位置上的值
	public static void swap(int[] arr, int i, int j) {
		arr[i] = arr[i] ^ arr[j];
		arr[j] = arr[i] ^ arr[j];
		arr[i] = arr[i] ^ arr[j];
	}

3、插入排序
分析:
数组:5 4 3 4 3 2
(1)先想0~0范围有序,只有一个数5
(2)0~1范围有序,把小的往前交换
4 5 3 4 3 2
(3)0~2范围有序,往前看交换
3 4 5 4 3 2
(4)重复这件事
这个算法和选择排序、冒泡排序是不一样的,数据的初始值对算法有影响
1 2 3 4 5 6 最好的例子:O(n)
6 5 4 3 2 1 最差的例子:O(n²)
一路交换就等于逆向冒泡

估计时间复杂度是用最差的例子,所以插入排序为O(n²)

	public static void insertionSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		
		// 0~0 有序的
		// 0~i 想有序
		for (int i=1; i<arr.length; i++) { // 0~i 做到有序
			
			// 如果j没有越界,j位置的数大于j+1位置的数,则交换
			for (int j=i-1; j>=0 && arr[j]>arr[j+1]; j--) {
				swap(arr, j, j+1);
			}
		}
	}
	
	// i和j是一个位置的,会出错
	public static void swap(int[] arr, int i, int j) {
		arr[i] = arr[i] ^ arr[j];
		arr[j] = arr[i] ^ arr[j];
		arr[i] = arr[i] ^ arr[j];
	}

七、时间复杂度注意

1、算法的过程,和具体的语言是无关的。
2、想分析一个算法流程的时间复杂度的前提,是对该流程非常熟悉。
3、一定要确保在拆分算法流程时,拆分出来的所有行为都是常数时间的操作。这意味着你写算法时,对自己的用过的每一个系统api,都非常的熟悉。否则会影响你对时间复杂度的估算。

八、额外空间复杂度

1、你要实现一个算法流程,在实现算法流程的过程中,你需要开辟一些空间来支持你的算法流程。
2、作为输入参数的空间,不算额外空间。作为输出结果的空间,也不算额外空间。因为这些都是必要的、和现实目标功能有关的。所以都不算。
3、但除此之外,你的流程如果还需要开辟空间才能让你的流程继续下去。这部分空间就是额外空间。
4、如果你的流程只需要开辟有限几个变量,额外空间复杂度就是O(1)。

九、算法流程的常数项

1、我们会发现,时间复杂度这个指标,是忽略低阶项和所有常数系数的。
2、难道同样时间复杂度的流程,在实际运行时候就一样的好吗?当然不是。
3、时间复杂度只是一个很重要的指标而已。如果两个时间复杂度一样的算法,你还要去在时间上拼优劣,就进入到拼常数时间的阶段,简称拼常数项。
4、拼常数项时间,申请大样本直接去跑
加减运算就是比乘除运算时间要短,加减运算一定不如位运算

十、算法流程的常数项的比拼方式

1、放弃理论分析,生成随机数据直接测。
2、为什么不去理论分析?
(1)不是不能纯分析,而是没必要。因为不同常数时间的操作,虽然都是固定时间,但还是有快慢之分的。
(2)比如,位运算的常数时间远小于算术运算的常数时间,这两个运算的常数时间又远小于数组寻址的时间。
(3)所以如果纯理论分析,往往会需要非常多的分析过程。都已经到了具体细节的程度,莫不如交给实验数据好了。

十一、什么是一个问题的最优解?

1、一般情况下,认为解决一个问题的算法流程,在时间复杂度的指标上,一定要尽可能的低,先满足了时间复杂度最低这个指标之后,使用最少的空间的算法流程,叫这个问题的最优解。
2、一般说起最优解都是忽略掉常数项这个因素的,因为这个因素只决定了实现层次的优化和考虑,而和怎么解决整个问题的思想无关。

十二、常见的时间复杂度

排名从好到差:
O(1)
O(logN)
O(N)
O(N*logN)
O(N^2) O(N^3) ... O(N^K)
O(2^N) O(3^N) ... O(K^N)
O(N!)

十三、算法和数据结构学习的大脉络

1、知道怎么算的算法
2、知道怎么试的算法
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值