【学】左程云 算法的 概念 与 性质

【左程云 01】算法的 概念 与 性质

算法要求:在保证时间复杂度最低的前提下,尽可能减小空间复杂度与常数项时间复杂度;


优劣算法评估
  • 时间复杂度(流程决定)
    • 忽略低阶项 与 高阶项的系数,留下来的就是时间复杂度;
    • 如一个流程最后得到的算式是 3 * N4 + 4 * N2 + C ,则最终时间复杂度就是 O( N4 )
  • 额外空间复杂度(流程决定)
    • 与功能无关,必须自己开辟的额外空间
    • 用户要求的功能,(如复制一个数组),则实现这个功能所使用的代码空间不算
  • 常数项时间(实现细节决定)
常数与非常数时间操作
  • 什么是常数时间操作?
    • 概念:一个数据改变偏移量,操作所需要的时间不变就是常数时间操作;

    • 实例:从数组中取第一个与取第100000个元素所需要的时间一样,则这就是一个常数操作;

    • 常见的此类操作:

      • 加 / 减 / 乘 / 除 / 取余 :+ - * / %
      • 常见的位运算操作(>> / >>> / << / | & / ^ 等)
      • 赋值、比较、自增、自减操作等
      • 数组寻址操作
  • 什么是常数时间操作:
    • 除了常数时间操作都是非常数时间操作;
    • 举例:LinkedList,寻址的时候需要遍历;
  • 排序算法中的常见优劣性

尽量可以背下来;


中文名称 英文名称 平均时间复杂度 最坏时间复杂度 最好时间复杂度 空间复杂度 稳定性
选择排序 Selection 1 ×
冒泡排序 Bubble n 1
插入排序 Insertion 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

你可能感兴趣的:(算法,排序算法,数据结构)