【左程云 01】算法的 概念 与 性质
算法要求:在保证时间复杂度最低的前提下,尽可能减小空间复杂度与常数项时间复杂度;
优劣算法评估
- 时间复杂度(流程决定)
- 忽略低阶项 与 高阶项的系数,留下来的就是时间复杂度;
- 如一个流程最后得到的算式是 3 * N4 + 4 * N2 + C ,则最终时间复杂度就是 O( N4 )
- 额外空间复杂度(流程决定)
- 与功能无关,必须自己开辟的额外空间
- 用户要求的功能,(如复制一个数组),则实现这个功能所使用的代码空间不算
- 常数项时间(实现细节决定)
常数与非常数时间操作
- 什么是常数时间操作?
-
概念:一个数据改变偏移量,操作所需要的时间不变就是常数时间操作;
-
实例:从数组中取第一个与取第100000个元素所需要的时间一样,则这就是一个常数操作;
-
常见的此类操作:
- 加 / 减 / 乘 / 除 / 取余 :
+
-
*
/
%
- 常见的位运算操作(>> / >>> / << / | & / ^ 等)
- 赋值、比较、自增、自减操作等
- 数组寻址操作
- 加 / 减 / 乘 / 除 / 取余 :
-
- 什么是非常数时间操作:
- 除了常数时间操作都是非常数时间操作;
- 举例:LinkedList,寻址的时候需要遍历;
- 排序算法中的常见优劣性
尽量可以背下来;
中文名称 | 英文名称 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|---|
选择排序 | Selection | n² | n² | n² | 1 | × |
冒泡排序 | Bubble | n² | n² | n | 1 | √ |
插入排序 √ | Insertion | n² | n² | n | 1 | √ |
堆排序 √ | Heap | n log2 n | n log2 n | n log2 n | 1 | × |
希尔排序 | Shell | n1.3 | n2 | n | 1 | × |
归并排序 √ | Merge | n log2 n | n log2 n | n log2 n | n | √ |
快速排序 √ | Quick | n log2 n | n2 | n log2 n | log2 n | × |
桶排序 | Bucket | n + k | n2 | n | n + k | √ |
计数排序 | Counting | n + k | n + k | n + k | n + k | √ |
基数排序 | Radix | n * k | n * k | n * k | n + k | √ |
举例:插入排序的最优与最差时间复杂度
- 最优例子:【0 1 2 3 4 5 6 】
- 最差例子:【6 5 4 3 2 1 0 】
合理使用对数器
- 认识对数器
- 想要测试的 方法 a
- 实现复杂度不好但使用以实现的 方法 b
- 实现一个随机样本产生器
- 把 方法 a 和 方法 b 跑相同的随机样本,看得到的结果是否一致
- 如果一个随机样本使比对结果不一致,打印样本进行人工干预,改对 方法 a 和 方法 b;
- 当样本数量很多时比对测试依然正确,则可以确定方法 a 已经正确。
算法示例(重要!)
- 从一个长度为 n 的 有序数组 中寻找一个数字 m;
方法一:遍历: O( n )
方法二:二分查找法: O( log2 N )
- 从一个长度为 n 的 无 序数组(相邻不等) 中寻找一个局部最小值(任意一个);
略
- 一个数组中有一种数出现了奇数次,其他的都是偶数次,找出这个奇数次的数字:
// 用一个 0 从头异或到尾,最后这个 0 数字就会变成 那个 奇数数字;
int eor = 0;
int[] arr = [ 2,1,3,2,2,4,1,3,1,2,4,1,4];
for(int i = 0;i < arr.size();i++){
eor = aor^arr.get(i);
}
- abcde 五个数字异或的结果一样,顺序无所谓( 满足 交换律 与 结合律 )
异或常用公式:
0 ^ N == N
N ^ N == 0
a b 两个数字交换值:(自己推导一下,很简单,不懂得可以看推导过程 A01。)
a = a ^ b
b = a ^ b
a = a ^ b
- 一个数字,取出最右侧的 1 ,如 14 二进制之后变成【 1 1
1
0】 取出下划线的1
如 要计算的是 a
则 b = a取反 + 1
a = a ^ b;
- 一个数组中有两种数(这两个数字不相等且 a ! = 0,b != 0 )出现了奇数次,其他的都是偶数次,找出这两种奇数次的数字:
根据第三题的理论:好比这两个数字是 a 和 b,eor 异或之后 得到的结果是 a ^ b(a != b)
根据第五题的理论:eor 一定在某一个位置有个1(记此位置为 n ),那这个位置对于 a 和 b 来说,一定不一样,(a 和 b 在这一位上一定一个是 0 一个是 1)
那整个数组一定可以分为两大类 :第n位为 0 的数 与 第n位为 1 的数,出现偶数次的数无论怎么分都无所谓,所以执行下面的步骤:
eor异或之前 + 一个条件,只要第 n 位为 0 (或者只异或 为 1的)
int rightOne = eor & (~eor + 1); // 取出最右侧的那个 1 int onlyOne = 0;// eor' for(int i = 0;i < arr.length;i++){ if((arr[i]) & rightOne)!=0){ onlyOne ^= arr[i]; } } System.out.println(onlyOne+""+(eor^onlyOne));
那么一个数已经出来了,就是eor,另一个数就是 eor ^ eor' ( eor 异或上 eor 取反)
- 找一个二进制数字中所有的 1
找出最右侧的 1 记为 rightOne
rightOne 与 这个数字 异或,这就消除了最右侧的 1
重复第一步与第二部,直到找不到 rightOne
补充
推导过程 A01
甲 乙 两个数字交换值,让int a = 甲,int b = 乙:
- a = a ^ b
此时 a = 甲 ^ 乙 ;b = 乙;
- b = a ^ b
此时 b = 甲 ^ 乙 ^ 乙;(由于 a 在上一步已经等于 “甲 ^ 乙” 了)
根据公式:N ^ N == 0,所以 b ^ b == 0;(异或满足交换定律,运算顺序无所谓)
根据公式:0 ^ N == N,所以 a ^ 0 == a , 所以 b = a;
- a = a ^ b
此时的 a == 甲 ^ 乙,b == 甲,所以这个算是可以写成:a = 甲 ^ 乙 ^ 甲;
所以得到 a = 乙(原理同第二步)
二分法注意事项:
方法一:mid = ( L + R ) / 2
改 进:mid = L + ( R - L ) / 2
N / 2 == N >> 1 (位运算速度比除运算快)
mid = L + ( R - L ) >> 1