大O标识法和时间复杂度:
度量一个程序的执行时间通常有两种方法
- 事后统计的方法
- 事前分析估算的方法 O
Ο(1)<Ο(log2(n))<Ο(n)<Ο(n^2)<Ο(n^3)<…<Ο(2^n)
Ο(1):如果算法的执行时间不随着问题规模n的增加而增长,即使算法中有上千条语句,其执行时间也不过是一个较大的常数
let a=12;
let b=13;
let temp=a;
a=b;
b=temp;
Ο(log2(n)):当数据增大 n 倍时,耗时增大 logn 倍(这里的 log 是以 2 为底的,比如,当数据增大 256 倍时,耗时只增大 8 倍)
let i=1;
while(i<=n){
i*=2;
}
Ο(n):数据量的增大几倍,耗时也增大几倍
for(i=1;i<=n;i++){
...
}
Ο(n^2):数据量增大 n 倍时,耗时增大 n 的平方倍
for(i=1;i<=n;i++){
for(j=1;j<=n;j++){
...
}
}
排序算法:
冒泡排序 O(n^2)
/*
* (N-1)+(N-2)+...+1 = N*(N-1)/2
* => N^2/2 - N/2
* => N^2/2 只取最高阶
* => N^2 去除常量
* => O(n^2)
*/
function swap(arr, i, j) {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
return arr;
}
Array.prototype.bubble = function bubble() {
// 外层循环I控制比较的轮数
for (let i = 0; i < this.length - 1; i++) {
// 里层循环控制每一轮比较的次数J
for (let j = 0; j < this.length - 1 - i; j++) {
if (this[j] > this[j + 1]) {
// 当前项大于后一项,交换位置
swap(this,j,j+1);
}
}
}
return this;
}
let ary = [12, 8, 24, 16, 1];
ary.bubble();
console.log(ary);
需要注意的是: 冒泡排序是可以做优化的
冒泡排序有两种优化方式
。
- 一种是外层循环的优化,我们可以记录当前循环中是否发生了交换, 如果没有发生交换,则说明该序列已经为有序序列了。
因此我们不需要再执行之后的外层循环,此时可以直接结束。 - 一种是内层循环的优化,我们可以记录当前循环中最后一次元素交换的位置 ,该位置以后的序列都是已排好的序列,因此下 一轮循环中无需再去比较。
完整优化后如下:
let arr = [42, 4, 35, 26, 3, 56, 346, 3, 46, 2]
//冒泡
for(var i = 0; i < arr.length - 1; i++) {
let flag = true //优化一
//优化二即为arr.length - i
for(var j = 0; j < arr.length -1 - i; j++) {
// console.log(1) //测试
if(arr[j] > arr[j + 1]) {
flag = false //优化一
let temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
if(flag) break //优化一
}
console.log(arr)
选择排序 O(n^2)
Array.prototype.select = function select() {
for (let j = 0; j < this.length - 1; j++) {
let min = j,
temp = null;
// 找到比当前项还小的这一项索引
for (let i = min + 1; i < this.length; i++) {
if (this[i] < this[min]) {
min = i;
}
}
// 让最小的项和当前首位交换位置
swap(this,min,j);
}
return this;
};
let ary = [12, 8, 24, 16, 1];
ary.select();
console.log(ary);
插入排序 O(n^2)
Array.prototype.insert = function insert() {
// 1.准备一个新数组,用来存储抓到手里的牌,开始先抓一张牌进来
let handle = [];
handle.push(this[0]);
// 2.从第二项开始依次抓牌,一直到把台面上的牌抓光
for (let i = 1; i < this.length; i++) {
// A是新抓的牌
let A = this[i];
// 和HANDDLE手里的牌依次比较(从后向前比)
for (let j = handle.length - 1; j >= 0; j--) {
// 每一次要比较的手里的牌
let B = handle[j];
// 如果当前新牌A比要比较的牌B大了,把A放到B的后面
if (A > B) {
handle.splice(j + 1, 0, A);
break;
}
// 已经比到第一项,我们把新牌放到手中最前面即可
if (j === 0) {
handle.unshift(A);
}
}
}
return handle;
}
let ary = [12, 8, 24, 16, 1];
ary.insert();
console.log(ary);
快速排序 O(n*log2(n))
Array.prototype.quick = function quick() {
// 4.结束递归(当数组中小于等于一项,则不用处理)
if (this.length <= 1) {
return this;
}
// 1.找到数组的中间项,在原有的数组中把它移除
let middleIndex = Math.floor(this.length / 2);
let middleValue = this.splice(middleIndex, 1)[0];
// 2.准备左右两个数组,循环剩下数组中的每一项,比当前项小的放到左边数组中,反之放到右边数组中
let aryLeft = [],
aryRight = [];
for (let i = 0; i < this.length; i++) {
let item = this[i];
item < middleValue ? aryLeft.push(item) : aryRight.push(item);
}
// 3.递归方式让左右两边的数组持续这样处理,一直到左右两边都排好序为止(最后让左边+中间+右边拼接成为最后的结果)
return quick(aryLeft).concat(middleValue, quick(aryRight));
}
let ary = [12, 8, 15, 16, 1, 24];
ary.quick();
console.log(ary);
希尔排序 O(n^1.3)
Array.prototype.shell = function shell() {
let gap = Math.floor(this.length / 2);
while (gap >= 1) {
for (let i = gap; i < this.length; i++) {
while (i - gap >= 0 && this[i] < this[i - gap]) {
swap(this, i, i - gap);
i = i - gap;
}
}
gap = Math.floor(gap / 2);
}
};
let arr = [58, 23, 67, 36, 40, 46, 35, 28, 20, 10];
arr.shell();
console.log(arr);
数组去重:
数组去重我归纳为四个思路:
-
准备一个数组[]辅助去重
-
准备一个对象{}辅助去重
-
啥也不准备
,直接在数组上操作 -
使用es6中的
数据结构Set
思路一: 准备一个数组[]
// 循环原有数组中的每一项,每拿到一项都往新数组中添加;添加之前验证新数组中是否存在这一项,不存在再增加;
let ary = [1, 2, 3, 1, 2, 1, 2, 3, 2, 1, 2, 3];
let newAry = [];
for (let i = 0; i < ary.length; i++) {
let item = ary[i];
if (newAry.includes(item)) {
continue;
}
newAry.push(item);
}
console.log(newAry);
思路二: 准备一个对象
function unique(ary) {
let obj = {
};
for (let i = 0; i < ary.length; i++) {
let item = ary[i];
if (obj[item] !== undefined) {
ary[i] = ary[ary.length - 1];
ary.length--;
i--;
continue;
}
obj[item] = item;
}
return ary;
}
let aa = [12, 23, 12, 15, 25, 23, 25, 14, 16];
aa = unique(aa);
思路三: 啥也不准备,直接开干
// 先分别拿出数组中的每一项A;用这一项A和“它后面的每项”依次进行比较,如果遇到和当前项A相同的,则在原来数组中把这一项移除掉;
let ary = [1, 2, 3, 1, 2, 1, 2, 3, 2, 1, 2, 3];
for (let i = 0; i < ary.length; i++) {
let item = ary[i];
for (let j = i + 1; j < ary.length; j++) {
let compare = ary[j];
if (compare === item) {
ary.splice(j, 1);
j--;
}
}
}
console.log(ary);
思路四: 数据结构Set
// 基于ES6的Set(对应的Map)实现去重
let ary = [12, 23, 12, 15, 25, 23, 25, 14, 16];
ary = [...new Set(ary)];
console.log(ary);
数组扁平化的5种实现办法:
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];
/*方案1:使用 Array.prototype.flat 处理*/
arr = arr.flat(Infinity);
/*方案2:把数组直接变为字符串即可*/
arr = arr.toString().split(',').map(item => {
return Number(item);
});
/*方案3:JSON.stringify*/
arr = JSON.stringify(arr).replace(/(\[|\])/g, '').split(',').map(item => Number(item));
/*方案4:基于数组的some方法进行判断检测*/
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
/*方案5:基于递归深度遍历*/
Array.prototype.myFlat = function myFlat() {
let result = [];
//=>循环数组中的每一项,把不是数组的存储到新数组中
let fn = (arr) => {
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (Array.isArray(item)) {
fn(item);
continue;
}
result.push(item);
}
};
fn(this);
return result;
}
数组的交差并补(基于ES6的Set)
let arr1 = [1, 2, 3, 4, 5],
arr2 = [5, 6, 7, 8, 9],
_arr1Set = new Set(arr1),
_arr2Set = new Set(arr2);
// 交集
let intersection = arr1.filter(item => _arr2Set.has(item));
// 并集
let union = Array.from(new Set([...arr1, ...arr2]));
// 补集 两个数组各自没有的集合
let complement = [
...arr1.filter(item => !_arr2Set.has(item)),
...arr2.filter(item => !_arr1Set.has(item))
];
// 差集 数组arr1相对于arr2所没有的
let diff = arr1.filter(item => !_arr2Set.has(item));
浅拷贝
object.assign()
var a = {
a : 'old', b : {
c : 'old'}}
var b = Object.assign({
}, a)
b.a = 'new'
b.b.c = 'new'
console.log(a) // { a: 'old', b: { c: 'new' } }
console.log(b) // { a: 'new', b: { c: 'new' } }
自己实现
// 只复制第一层的浅拷贝
function simpleCopy(obj1) {
var obj2 = Array.isArray(obj1) ? [] : {
};
for (let i in obj1) {
obj2[i] = obj1[i];
}
return obj2;
}
var obj1 = {
a: 1,
b: 2,
c: {
d: 3
}
}
var obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
alert(obj1.a); // 1
alert(obj2.a); // 3
alert(obj1.c.d); // 4
alert(obj2.c.d); // 4
如果是数组的话,还可以使用Array的slice和concat方法 返回新数组
let arr =[1,2,3,[1,2],{
a:"b"}]
// let arr2 = arr.slice(0)
let arr2 = [].concat(arr)
arr2[4].a="bbbbbb"
console.log(arr)
console.log(arr2)
深拷贝:
JSON.stringify与JSON.parse
缺点: 在JSON.stringify()做序列时,undefined、任意的函数以及symbol值,在序列化过程中会被忽略
例子:
let obj ={
name:"AFwf",
age:21,
hobbies:["Fse","fsef","Sfg","Sfgse"],
nothing:undefined,
}
let obj2 = JSON.stringify(obj)
console.log(obj2)
输出图:
可以看到nothing这个属性没有被深拷贝过来,这显然不是我们想要的
自己实现深拷贝
let obj = {
name: "AFwf",
age: 21,
hobbies: ["Fse", "fsef", "Sfg", "Sfgse"],
nothing: undefined,
}
let obj2 = {
}
deepClone(obj, obj2)
console.log(obj2)
function deepClone(origin, target) {
let target = target || {
},
toStr = Object.prototype.toString
arrStr = "[object Array]"
for(let prop in origin) {
//避免拿原型链上的属性
if(origin.hasOwnProperty(prop)) {
//判断是否是引用类型
if(origin[prop] !== null && typeof(origin[prop]) == 'object') {
//判断是数组引用,还是对象引用
if(toStr.call(origin[prop]) == arrStr) {
//数组
target[prop] = []
} else {
//对象
target[prop] = {
}
}
//递归入口
deepClone(origin[prop], target[prop])
} else {
//递归的出口
target[prop] = origin[prop]
}
}
}
//没有传target,就把内部生成的target返回出去
return target
}
输出图:这次看到nothing没有被丢弃了,舒服了