认识时间复杂度
常数时间的操作
一个操作如果和样本的数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。
时间复杂度为一个算法流程中,常数操作数量的一个指标。常用O(读作big O)来表示。具体
来说,先要对一个算法流程非常熟悉,然后去写出这个算法流程中,发生了多少常数操作,
进而总结出常数操作数量的表达式。
在表达式中,只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分如果为f(N),那
么时间复杂度为O(f(N))。
已选择排排序为例子
[4, 5, 2, 3, 6, ..... , m] 有n个数
数据量 比较次数 交换次数
第一次:[0]~[n-1] n-2 1
第二次:[0]~[n-2] n-3 1
第三次:[0]~[n-3] n-4 1
... ... ... 1
第n-1次 [0]~[1] 1 1
常数操作为:1+...+(n-3)+(n-2)+(n-1)+ 1+(n-4) +(n-3)+(n-2) + 1 + 1 + 1
最后表达式可表示为:f(N) = aN² + b N + c
去最高阶项,且忽略其系数,时间复杂度为O(N²)
评价一个算法流程的好坏,先看时间复杂度的指标,然后再分析不同数据样本下的实际运行
时间,也就是“常数项时间”。
public static void prrocess1(){ // 时间复杂度 O(n)
int N = 1000;
int a = 0;
for(int i=0; i<N; i++){
a = 2+5;
a = 4*7;
a = 6*8;
}
}
public static void prrocess2(){ // 时间复杂度 O(n)
int N = 1000;
int a = 0;
for(int i=0; i<N; i++){
a = 3 | 6;
a = 3 & 4;
a = 4 ^ 785;
}
}
// prrocess1和prrocess2的时间复杂度一样,实际运行时间决定算法流程的好坏
选择排序、冒泡排序细节的讲解与复杂度分析
/*
[7,3,4,3,6,5]
0,1,2,3,4,5
第一次循环:[0]~[n-1] 排好1个数
[0]~[1] [3,7,4,3,6,5]
[1]~[2] [3,4,7,3,6,5]
[2]~[3] [3,4,3,7,6,5]
[3]~[4] [3,4,3,6,7,5]
[4]~[5] [3,4,3,6,5,7]
第一次循环:[0]~[n-3] 排好1个数
第一次循环:[0]~[n-4] 排好1个数
....
最后表达式可表示为:f(N) = aN² + b N + c
去最高阶项,且忽略其系数,时间复杂度为O(N²)
*/
public static
void selectionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
// 额外空间O(1) 包括:i、minUdex、j
for (int i = 0; i < arr.length - 1; i++) { // 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;
}
时间复杂度O(N^2),额外空间复杂度O(1)
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
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位置上的值 异或预算 不同为1 相同为0
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];
}
插入排序细节的讲解与复杂度分析
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
// 0~0 有序的
// 0~1 想有序
for (int i = 1; i < arr.length; i++) { // 0~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];
}
时间复杂度O(N^2),额外空间复杂度O(1)
额外空间复杂度O(1):可理解为开辟有限个空间,例如:int i = 1
算法流程按照最差情况来估计时间复杂度
二分法的详解与扩展
1)在一个有序数组中,找某个数是否存在
2)在一个有序数组中,找>=某个数最左侧的位置
3)局部最小值问题
异或运算的性质与扩展
1)0^N == N N^N == 0
2)异或运算满足交换律和结合率
3)不用额外变量交换两个数
4)一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到这一个数
5)一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到这两个数加粗样式
异或运算:
1)、0 ^ N = N N^N =0
2)、满足交换和结合
a^b = b^a a^b^c = a^(b^c)
3)、集合 ^
a:1 0 1 1 0
b:0 0 1 1 1
----------------------------
^:1 0 0 0 1
&:0 0 1 1 0
分析:
int a=甲;
int b=乙;
a=a^b; a=甲^乙 b=乙
b=a^b; b=甲^乙 b=甲^乙^乙=甲^0=甲
a=a^b; a=甲^乙^甲=0^乙=乙 b=甲
由异或运算衍生的算法题:
一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数?
思路: 一直由异或运算即可
public static void printOddTimesNum1(int[] arr) {
int eO = 0;
for (int cur : arr) {
eO ^= cur;
}
System.out.println(eO);
}
一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这两种数?
思路:
1、eor = a^b
2、cur & rightOne 只能是a或者b
3、再次^
提取出最右的1的原因:a^b至少有一个二进制位上是一个地方不同,一个为0,一个为1,并以此分类。
假设该位置为21位,a的21位为1,b的21位为0(总共是32位)。因此能够将数组所有数分为2类。 21位为1(有a无b)、21位为0(有b无a)。
最后仅需选择一类进行&,即可得出a或者b
public static void printOddTimesNum2(int[] arr) {
int eor = 0;
for (int curNum : arr) {
eor ^= curNum;
}
// eor = a^b
// eor ! = 0
// eor必然有一个位置上是1
int eorhasOne = 0;
int rightOne = eor & (~eor + 1);// 提取出最右的1
/*
eor: 1010111100
~eor: 0101000011
~eor+1: 0101000100
eor & (~eor + 1) 0000000100
*/
*/
for (int cur : arr) {
if ((cur & rightOne) != 0) {
eorhasOne ^= cur;
}
}
System.out.println(eorhasOne + " " + (eor ^ eorhasOne));
}