直击算法,剖析前端高频算法(含排序算法,去重,数组扁平化,交差并补,深浅拷贝)

29 篇文章 0 订阅
1 篇文章 0 订阅

大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);

数组去重:

数组去重我归纳为四个思路

  1. 准备一个数组[]辅助去重

  2. 准备一个对象{}辅助去重

  3. 啥也不准备,直接在数组上操作

  4. 使用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));

浅拷贝

  1. 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' } }
  1. 自己实现
// 只复制第一层的浅拷贝
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
  1. 如果是数组的话,还可以使用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)

深拷贝:

  1. 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这个属性没有被深拷贝过来这显然不是我们想要的

  1. 自己实现深拷贝
		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没有被丢弃了,舒服了
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

codingWeb

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值