前言
虽然作为前端,目前接触到的数据结构和算法业务应用并不很多,但是要想有进一步的提升,学习这些是很有必要滴,所以,在这里记录下关于算法的一些扫盲问题,算是入门,也算是为了方便回顾和进步。
算法复杂度
算法复杂度分为
时间复杂度
和空间复杂度
- 时间复杂度:指执行算法所需要的计算工作量(时间)
- 空间复杂度:指这个算法所需要的内在空间。
时间复杂度
时间复杂度用O(X)表示
- 常数时间:O(1),表示执行算法的时间与数据量大小无关
- 线性时间复杂度:O(N), N表示数据量。【对于一个算法来说,可能操作次数
aN+1
,该算法的时间复杂度则为O(N),可能两个算法都是O(N)的复杂度,此时要通过对比低阶项和常数项了】 - 对数时间复杂度:O(logN),由于计算机使用二进制的记数系统,对数常以2为底
位运算
位运算相对于四则运算(±*/)通常会快很多,所以这在算法运算中很有用
位运算就是直接对整数在内存中的二进制位进行操作,所以在学习位运算前,要知道二进制与十进制之间的转换过程
- 十进制
33
可以看成是32 + 1
,并且 33 应该是六位二进制的(因为33
近似32
,而 32 是 2 的五次方,所以是六位),那么 十进制33
就是100001
,只要是 2 的次方,那么就是 1 否则都为 0 - 那么二进制
100001
同理,首位是2^5
,末位是2^0
,相加得出 33
1. 左移 <<
/*
* 10 --> 二进制 1010
* 1010 --> 左移一位 10100
* 10100 --> 十进制 20
*/
10 << 1
2. 右移>>
/*
* 10 --> 二进制 1010
* 1010 --> 右移一位 101
* 101 --> 十进制 5
*/
10 >> 1
按位操作
1. 按位与&
每一个二进制位都为1,结果才为1
/*
* 8--> 二进制 1000
* 7--> 二进制 0111
* 1000 & 0111 -> 0000 -> 0
*/
8&7
2. 按位或|
其中一位为1,结果就是1
/*
* 8--> 二进制 1000
* 7--> 二进制 0111
* 1000 | 0111 -> 1111 -> 15
*/
8|7
4. 按位异或^
每一位都不同,结果才为1
/*
* 8--> 二进制 1000
* 7--> 二进制 0111
* 8^8 --> 1000 ^ 1000 --> 0000
* 8^7 --> 1000 ^ 0111--->1111
*/
8^7 // -> 15
8^8 // -> 0
一道leetCode算法题(序号136)
通过按位异或来解可以实现O(N)的时间复杂对,且空间复杂度也相对更低
/**
* @param {number[]} nums
* @return {number}
*/
var singleNumber = function(nums) {
var ans = 0;
for(let num of nums){
ans ^=num; //这里借助相同数的^操作结果为0来实现
}
return ans;
};
singleNumber([4,1,2,1,2]); // 4
/*
* 过程解析:
* 1. num=4, 0^=4-->二进制 100
* 2. num=1, 4^=1-->二进制 100^001-->101
* 3. num=2, 二进制 101^= 010 --》 111
* 4. num=1, 二进制 111^= 001 ---> 110
* 5. num=2, 二进制 110^= 010 ---> 100 -->十进制 4
*/
排序算法
常见的排序算法有:冒泡排序,插入排序,选择排序,归并排序,快非(二分法)。。。
- 经典参考
1. 冒泡排序
/*
* 思路:冒泡排序每次两两比较,将大的一位排向后边,每一轮都会将最大的排到最后
*/
function bubbleSort(arr){
//因为冒泡排序算法是两两比较 所以外层比较次数应为数组长度-1
for(var i = 0; i<arr.length - 1; i++){
// 内循环的比较不必全部执行完 因为每一轮的内循环都会将最大的数排在最右
// 所以后面的次数不用比较 所以内循环的次数是递减的 需要减去一个i
for(var j = 0; j < arr.length -1 - i; j++){
if(arr[j] > arr[j+1]){
var t = arr[j];
arr[j] = arr[j+1];
arr[j+1] = t;
}
}
console.log(arr)
}
return arr;
}
2. 插入排序
原理:第一个元素默认是已排序元素,取出下一个元素和当前元素比较,如果当前元素大就交换位置。那么此时第一个元素就是当前的最小数,所以下次取出操作从第三个元素开始,向前对比,重复之前的操作。
3. 选择排序
原理:遍历数组,设置最小值的索引为 0,如果取出的值比当前最小值小,就替换最小值索引,遍历完成后,将第一个元素和最小值索引上的值交换。如上操作后,第一个元素就是数组中的最小值,下次遍历就可以从索引 1 开始重复上述操作。
4. 快速排序
原理:随机选取一个数组中的值作为基准值,从左至右取值与基准值对比大小。比基准值小的放数组左边,大的放右边,对比完成后将基准值和第一个比基准值大的值交换位置。然后将数组以基准值的位置分为两部分,继续递归以上操作。
var arr = [3, 4, 2, 6, 5, 8, 9, 10, 16, 13]; // length = 10
function quickSort(arr) {
if (arr.length <= 1) { return arr; }
// 基准值
var pivot = arr[Math.floor(arr.length / 2)];
arr.splice(arr.indexOf(pivot), 1);
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
}
console.log(quickSort(arr));