算法学习
b 站教学视频:https://www.bilibili.com/video/BV1Ry4y1L7CR?p=2
1.认识复杂度和简单排序
常数
常数操作+ - * / >> <<
数组的寻址:int a = arr[i]
都属于常数
非常数操作:链表的寻址:int b = list.get(i)
时间复杂度
选择排序: 从 0 的位置循环对比 将最小的放到 0 的位置, 然后从 1 的位置循环对比 将最小的放到 1 的位置,... 一直到第 N 项
function selectSort(arr) {
for (let i = 0; i < arr.length; i++) {
let mianIndex = i;
for (let j = i + 1; j < arr.length; j++) {
mianIndex = arr[mianIndex] < arr[j] ? mianIndex : j;
}
swap(arr, i, mianIndex);
}
return arr;
}
function swap(arr, i, j) {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
时间复杂度 O(N^2)
首先估计 需要进行的常数操作
写出常数操作的公式
看: N-1 + N-2 + N-3 ...
比较:N-1 + N-2 + N-3 ...
交换:N
= aN^2 + bN + c
取最复杂的情况就是aN^2
,去掉常数系数a
, 使用O()
包上就是时间复杂度 O(N^2)
O(N)
优与O(N^2)
分析一个算法的好坏、先对比时间复杂度指标、指标相同的情况对比实际运行时间(常数操作时间)
分类:
O():代表最差情况的常数操作
θ():代表平均情况
Ω():代表最好情况
冒泡排序:
从 0 的位置对比 1 的位置,如果 0 的位置>1 的位置,两个数做交换、继续从 1 的位置对比 2 的位置,...一直到第 N -1 项
function bubbleSort(arr) {
for (let e = arr.length - 1; e > 0; e--) {
for (let i = 0; i < e; i++) {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);
}
}
}
return arr;
}
function swap(arr, i, j) {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
时间复杂度 O(N^2)
异或运算
性质 | 值 |
---|---|
性质 1 | 0^a =a ; a^a=0; |
性质 2 | a^b === b^a; (ab)c === a(bc); |
性质 3 | 同一组数异或,结果不受先后影响, 一堆数怎么先后异或结果都相同 |
使用异或实现 swap 方法(互换两个变量的值)
function swap(arr, i, j) {
arr[i] = arr[i] ^ arr[i];
arr[j] = arr[i] ^ arr[i];
arr[i] = arr[i] ^ arr[i];
}
注意:这种方式操作的前提要保证 a 和 b 在为内存中两个独立的区域,如果 a 和 b 相同 a 和 b 都会变成 0,为了程序的严谨性不要使用这种方式进行两个变量的交换
实现原理:
int a=甲;
int b=乙;
a=a^b; //a=甲^乙 b=乙
b=a^b; //a=甲^乙 b=乙^甲^乙=0^甲=甲
a=a^b; //a=甲^乙^甲=0^乙=乙 b=甲
面试题
给出一个数组,该数组全部为 int 型数据,
( 时间复杂度为 O(N),空间复杂度为 O(1) )
(1)数组中有一种数为奇数次,其他数为偶数次,求奇数次的数字
思路:把所有数异或 ,最后剩下的肯定是奇数次的数
(2)数组中有两种数为奇数次,其他数为偶数次,求奇数次的数字分别是什么
思路:
在整型数的 32 位中,他们必然有 1 位不一样,而这位就是它们异或
后结果为 1 的那位,我们通过这一位将整个数组分成两份,使其中一份
异或就可以得到,两个数其中一个数。再异或它俩的异或结果就可以得
到另一个数
// 有两个奇数次的变量 a 和 b
function diffTwo(arr) {
let eor = 0;
for (let item of arr) {
eor ^= item;
}
// eor = a ^ b
// eor != 0
// eor 必然有一个位置为1
let rightOne = eor & (~eor + 1); // 取出最右侧的1 比如:00000000001
let onlyOne = 0;
for (let item of arr) {
// item 与 00000000001 & 出来的肯定 是最后一位为1的 否则是0
if ((item & rightOne) == 1) {
// onlyOne 不是 a 就是 b
onlyOne ^= item;
}
}
// a,b 或者 b,a
return [onlyOne, eor ^ onlyOne];
}
插入排序
类似捋牌,从左边第 2 张牌开始,从右往左划到合适的位置插入进去
1-2 是否正确排序:1 的位置和 2 的位置对比是否需要换位,
- 需要换位那就换,然后从 1 向左比较看是否需要换位(因为 1 左没有了所以进入 next)
- 需要换位就换位继续向左比较
- 否者 next
- 否者 nex
2-3 是否正确排序:那从 2 对比 3 的位置,是否需要换位,
- 需要换位那就换,然后从 2 向左比较看是否需要换位
- 需要换位就换位继续向左比较
- 否者 next
- 否者 next
重复操作直到数组最后一位
// 原始代码
function insertSort(arr) {
// 0 不用管,i负责将指针往右挪
for (let i = 1; i < arr.length; i++) {
// j负责将指针往左挪进行对比替换, 一旦i-1的位置大于i位置就替换,并且j-- 继续往左比
for (let j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
return arr;
}
// 好理解的
function insertSort(arr) {
// 0 不用管,i负责将指针往右挪
for (let i = 1; i < arr.length; i++) {
// j负责将指针往左挪进行对比替换, 一旦j - 1的位置大于j位置就替换,并且j-- 继续往左比
for (let j = i; j > 0 && arr[j - 1] > arr[j]; j--) {
swap(arr, j, j - 1);
}
}
return arr;
}
时间复杂度 O(N^2),但在数比较顺的情况,性能优于 冒泡和选择
2 分法
在一个有序数组中看某个数是否存在,
正常遍历:时间复杂度 O(N)
2 分法: 时间复杂度 O(log2N)
从数组中知道中间位置的数字 与 目标数对比,
小于就在左侧数组中继续使用中间数对比
大于就在右侧数组中继续使用中间数对比
等于就直接返回
log2N 代表 N 的对数: N = 8 2^3=N log2N = 3 相当于只有 3 次常数操作 比 O(N) 更小
在一个有序数组中看某个数的最左侧的位置
2 分法:
使用中间位置对比目标数
大于或等于都往左找同时使用变量记录下标
小于就回到上回定位的下标的位置往右中间数继续对比
最后剩下两个数后取左边的
局部最小值
一个无序数组、相邻两数肯定不相等、求 局部最小值,也就是是 i - 1 , i , i + 1 这三个位置上的最小值
2 分法:直接用 2 分一直往左找一定能找到最小值
2 分法判断场景:存在左右两侧可以甩掉一边的场景就可以用 2 分
对数器
将已有成熟算法跟自己的算法进行 50 万次结果对比看是否都对
function randomArray(maxSize, maxValue) {
let arr = Array.from({ length: parseInt((maxSize + 1) * Math.random()) });
arr.map((item) => (maxValue + 1) * Math.random() - maxValue * Math.random());
return arr;
}
function comparison() {
let testTime = 50000;
let maxSize = 100;
let maxValue = 100;
let flag = true;
for (let i = 0; i < testTime; i++) {
let arr1 = randomArray(maxSize, maxValue);
let arr2 = arr1.slice();
if (arr1.toString() !== arr2.toString()) {
flag = false;
break;
}
}
if (flag) {
console.log("comparison OK!");
} else {
console.log("comparison error!");
}
}