1 认识复杂度、对数器、二分法与异或运算
评估算法优劣的核心指标是什么?
- 时间复杂度(流程决定)
- 额外空间复杂度(流程决定)
- 常数项时间(实现细节决定)
何为常数时间的操作?
如果一个操作的执行时间不以具体样本量为转移,每次执行时间都是固定时间。称这样的操作为常数时间的操作。
总之,执行时间固定的操作都是常数时间的操作。
反之,执行时间不固定的操作,都不是常数时间的操作。
数组(常数操作)和列表
数组在内存空间中,是连续的区域;
列表在内存中不是连续的,而是一个指针指向下一个指针。
常数时间的操作:一个操作如果和样本的数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。比如数组的寻址操作,得到arr[i]的值,只需要起始地址+i就能算到该位置的地址,得到该位置上得值,是常数操作。而列表的寻址,就要逐个遍历,才得到list[i],这就不是常数操作。
如何确定算法流程的总操作数量与样本数量之间的表达式关系?
1,想象该算法流程所处理的数据状况,要按照最差情况来。
2,把整个流程彻底拆分为一个个基本动作,保证每个动作都是常°数时间的操作。
3,如果数据量为N,看看基本动作的数量和N是什么关系。
如何确定算法流程的时间复杂度?
当完成了表达式的建立,只要把最高阶项留下即可。低阶项都去掉,高阶项的系数也去掉。
记为:〇(忽略掉系数的高阶项)
选择排序
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零:
先找最小,次小,次次小…如此类推,一共n轮排序,每轮排序记录次轮比较中最小元素的位置,然后交换。
冒泡排序
冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。 它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。 这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
插入排序
i处与左边比较进行,如果小则进行交换,如果还比次左边小则再进行交换
额外空间复杂度
你要实现一个算法流程,在实现算法流程的过程中,你需要开辟一些空间来支持你的算法流程。
作为输入参数的空间、不算额外空间。
作为输出结果的空间,也不算额外空间。
因为这些都是必要的、和现实目标有关的。所以都不算。
但除此之外,你的流程如果还需要开辟空间才能让你的流程继续下去。这部分空间就是额外空间。如果你的流程只需要开辟有限几个变量,额外空间复杂度就是O(1)。
申请有限的变量,后面会此释放空间
最优解是什么意思?
一般情况下,认为解冲—人-..上,一定要尽可能的低,先满足了时间复杂度最低这个指标之后,
使用最少的空间的算法流程,叫这个问题的最优解。
一般说起最优解都是忽略掉常数项这个因素的,因为这个因素只决定了实现层次的优化和考虑,而和怎么解决整个问题的思想无关。
认识对数器
1,你想要测的方法a
2,实现复杂度不好但是容易实现的方法b
3,实现一个随机样本产生器
4,把方法a和方法b跑相同的随机样本,看看得到的结果是否一样
5,如果有一个随机样本使得比对结果不一致,打印样本进行人工干预,改对方法a和方法b
6,当样本数量很多时比对测试依然正确,可以确定方法a已经正确。
认识二分法
1)在一个有序数组中,找某个数是否存在
2)在一个有序数组中,找>=某个数最左侧的位置
3)在一个有序数组中,找<=某个数最右侧的位置
4)局部最小值问题
两变量交换(三种方法)
1.借助中间量交换
int temp = a;
a = b;
b = temp;
原理:类似于两杯水互换杯子,需要借助第三个杯子来实现。
2.位移运算交换(对不同地址、内存进行运算,不是直接对数值进行运算)
a = a ^ b;
b = a ^ b;
a = a ^ b;
原理:一个数对另一个数异(化成二进制数字只要两边相同就为false,不同则为true)或两次,该数不变。
a = a ^ b; 此时a的值为a ^ b
b = a ^ b; 此时的a经上面的运算变成a ^ b,所以b = a ^ b ^ b = a;
a = a ^ b; 此时的b又经上面的运算变成 a,所以a = a ^ b ^ a = b; 实现两个数的互换
3、数值相加减交换
a = a + b;
b = a - b;
a = a - b;
原理:通过先求得两数的和再进行减运算从而互换。
a = a + b; 此时a的值为10+20=30;
b = a - b; 此时的a经上面的运算变成两数的和30,所以b = 30 - 20(原先b的值) = 10(原先a的值);
a = a - b; 此时的b经上面的运算变成10,所以a = 30 - 10(原先a的值) = 20(原先b的值)从而实现两个数的互换 。
例题
1)数组中,一个数出现了奇数次,其他数出现了偶数次,要找奇数次的数
答案:将所有数都异或,最后只剩下奇数次的数。
public static void printOddTimesNum1(int[] arr) {
int eor=0;
for(int i=0;i<arr.length;i++){
eor^=arr[i];
}
System.out.println(eO);
}
最后异或之后只是剩下4
(2)提取出N的右侧第一个不为0的数ANS=(N&((~N)+1))
+1:将右侧的数字转变为原来的数字
(2)数组中,2种数a,b出现了奇数次,其他数出现了偶数次,要找奇数次的数
答案:将所有数都异或,得到eor=a异或b;因为a不等于b,所以eor!=0,必造成eor有一个位上为1,那个位就能用来区分a和b。
例如找到第8位可以区分a与b值不一样,那么此位为1或者0,若b在此位为1,那么设eor`=所有第8位为1的数进行异或,因为其他有偶数个,只有b为奇数个。那么eor`=b,eor^eor`=a。
例如:a=4,b=9;随机选择第3位不为1,发现4、6、12的第3位都不为0,那么设eor`=4(所有的1、6、12异或)
另外一个奇数个常数为eor^eor`=9
//计算二进制数字中有多少个1
public static int bit1counts(int N){
int count=0;
//011011010000 N
//000000010000 rightOne
//011011000000 N^=rightOne
while(N!=0){
int rightOne=N&((~N)+1);
count++;
N^=rightOne;
}
return count;
}