原文链接:https://github.com/helloyoucan/knowledge
算法
1、冒泡排序
比较两个相近的项,如果前一个大于后一个则交换位置。
时间复杂度O(n²)
function bubbleSort(arr){
const _arr = [...arr]; //copy array
const length = _arr.length;
for(let i = 0; i<length; i++){
for(let j = 0; j<length; j++){
if(_arr[j] > _arr[j+1]){//从小到大排序
const temp = _arr[j]
_arr[j] = _arr[j+1]
_arr[j+1] = temp
// [_arr[j+1],_arr[j]] = [_arr[j],_arr[j+1]]; //es6解构的写法
}
}
}
}
改进后的冒泡排序
每次最大值放到最右后,会将本轮最后一个操作的位置作为下一轮的终点,可以减少不必要的一些冒泡
function buddleSort (arr) {
let i = arr.length - 1
while(i>0){
let pos =0;
for (let j=0;j<i; j++) {
if(arr[j] > arr[j+1]){
pos = j;
[arr[j],arr[j+1]] = [arr[j+1],arr[j]]
}
}
i = pos;
}
return arr
}
2、快速排序
选择一个元素为基准,把小于等于基准的放到一个数组1,大于基准的放另外一个数组2,递归调用,直到数组被分割成只有一个元素,结束递归。连接按基准划分的数组。
时间复杂度O(n²)
function quickSort(arr){
//如果数组<=1,则直接返回
if(arr.length<=1){return arr;} //递归结束条件
const _arr = [...arr];
const pivotIndex = Math.floor(_arr.length/2); //取中间的索引,不是整数则舍去小数
const pivot = _arr.splice(pivotIndex,1)[0]; //找到基准并从源数组中移除
const left_arr = [];
const right_arr = [];
// 遍历,把比基准小的放在left_arr,比基准大的放在right_arr
for(let i = 0; i<_arr.length; i++){
if(_arr[i] <= pivot){
left_arr.push(_arr[i]);
}else{
right_arr.push(_arr[i]);
}
}
// 递归调用,并连接数组
return [...quickSort(left_arr), pivot, ...quickSort(right_arr)];
}
3、选择排序
找到最小的元素放在第一位,第二小的放在第二位,如此类推。
时间复杂度O(n²)
function selectionSort(arr){
const _arr = [...arr];
const length = _arr.length;
let minIndex = 0;
for(let i = 0; i<length -1; i++){
for(let j = i+1; j<length; j++){
if(_arr[j] < _arr[minIndex]){ // 寻找最小的数
minIndex = j; // 保存最小数的索引
}
}
[_arr[i], _arr[minIndex]] = [_arr[minIndex],_arr[i]]; //交换位置
}
return _arr
}
4、插入排序
- 从第一个元素开始,该元素可以被认为已经被排序
- 取出下一个元素,在已经排好序的序列中从后往前扫描
- 直到找到小于或者等于该元素的位置
- 将该位置后面的所有已排序的元素从后往前依次移一位
- 将该元素插入到该位置
- 重复步骤2~5
时间复杂度O(n²)
function insertionSort(arr){
const _arr = [...arr];
let j;
for(let i = 1; i<_arr.length; i++){
j = i - 1;
const temp = _arr[i] //为该元素找到合适的位置插入
while(j >= 0 && _arr[j] > temp){
_arr[j+1] = _arr[j]
j--;
}
_arr[j+1] = temp; //j+1最后为元素适合的位置
}
return _arr
}
5、归并排序
归并排序是一种分治算法。本质上就是把一个原始数组切分成较小的数组,直到每个小数组只有一个位置,接着把小数组归并成较大的数组,在归并过程中也会完成排序,直到最后只有一个排序完毕的大数组。
时间复杂度O(nlog^n)
// 分割数组,直到每个数组的元素只有一个
function mergeSort(arr){//采用自上而下的递归方法
const _arr = [...arr];
let length = _arr.length;
if(length<2){
return arr;
}
let middle = Math.floor(length/2);
let left_arr = _arr.slice(0,middle);
let right_arr = _arr.slice(middle);
return merge(mergeSort(left_arr),mergeSort(right_arr));
}
// 从数组中挑选小的元素插入到result[],实现排序(实际上是一个个挑选进行排序)
function merge(left,right){
let left_arr = [...left];
let right_arr = [...right];
let result = [];
while(left_arr.length && right_arr.length){
if(left_arr[0]<=right_arr[0]){//对比挑选小的元素进入result[]
result.push(left_arr.shift());
}else{
result.push(right_arr.shift());
}
}
result.push(...left_arr);
result.push(...right_arr);
return result;
}
6、堆排序
堆排序:堆排序把数组当中二叉树来排序而得名。
1)索引0是树的根节点;
2)除根节点为,任意节点N的父节点是N/2;
3)节点L的左子节点是2L;
4)节点R的右子节点为2R + 1。
本质上就是先构建二叉树,然后把根节点与最后一个进行交换,然后对剩下对元素进行二叉树构建,进行交换,直到剩下最后一个。
function heapSort(arr){
let _arr = [...arr];
// 初始化大顶堆(所有父节点都是与子节点比较中最大的节点),从第一个非叶子节点开始
for(let i = Math.floor(_arr.length/2 - 1); i>= 0; i--){
_arr = shiftDown(_arr,i,_arr.length);
}
// 排序,每一次for循环找出一个最大值,数组长度减一,然后继续找第二个最大的,如此类推
for(let i = Math.floor(_arr.length - 1); i>0; i--){
[_arr[0], _arr[i]] = [_arr[i], _arr[0]]; //根节点与最后一个节点交换
/*
从根节点开始调整,并且最后一个节点已经为当前最大值,
不需要再参与比较,所以第三个参数为i,即比较到最后一个节点的前一个即可
*/
_arr = shiftDown(_arr,0,i);
}
return _arr;
}
/*
将i节点以下的堆整理为大顶堆,
主要这一步实现的基础上是:假设节点i以后的子对已经是一个大顶堆
该函数的实际功能是:
找到节点i的堆中的正确位置。
后面将写一个for循环,从第一个非叶子节点开始,
对每一个非叶子节点都执行shiftDown操作,
所以就满足了节点i以下的子堆已经是一大堆顶
*/
//每次对比三个节点,将最大的节点换到父节点的位置
function shiftDown(arr,i,length){
const _arr = [...arr];
let temp = _arr[i] // 当前父节点
// j<length 的目的是对节点i以下的节点全部按顺序调整
for(let j = 2*i+1; j<length; j = 2*j+1){
temp = _arr[i]; //将arr[i]取出,整个过程相当于找到arr[i]应处的位置
if(j+1<length && _arr[j] < _arr[j+1]){
j++; //找到两个孩子中较大的一个,再与父节点比较
}
if(temp < _arr[j]){ //如果父节点小于子节点:
[_arr[i], _arr[j]] = [_arr[j], _arr[i]]; //交换
i = j; //交换后,temp的下标变为j
}else{//否则,退出
break;
}
}
return _arr;
}
7、希尔排序
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进行k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
简单插入排序的改进版,它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
O(nlog n)
function shellSort(arr){
const _arr = [...arr];
const length = _arr.length;
let temp = 0;
let gap = 1;
while(gap < length / 5){ //动态定以间隔序列
gap = gap*5+1;
}
while(gap > 0){
for(let i = gap; i< length; i++){
temp = _arr[i];
let j = 0;
for(j = i - gap; j >= 0 && _arr[j] > temp; j-=gap){
_arr[j+gap] = _arr[j];
}
_arr[j+gap] = temp;
}
gap = Math.floor(gap / 5);
}
return _arr;
}
8、二分查找
(1)首先,从有序数组的中间的元素开始搜索,如果该元素正好是目标元素(即要查找的元素),则搜索过程结束,否则进行下一步。
(2)如果目标元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半区域查找,然后重复第一步的操作。
(3)如果某一步数组为空,则表示找不到目标元素。
非递归算法
function binarySearch(arr , key){
let low = 0;
let high = arr.length - 1;
while(low <= high){
let mid = parseInt((high + low) / 2);
if(key === arr[mid]){
return mid;
}else if(key > arr[mid]){
low = mid + 1;
}else if(key < arr[mid]){
high = mid - 1;
}
}
return - 1;
}
递归算法
function binarySearch(arr , key, low, high){
low = low||0;
high =high||arr.length - 1;
if(low > high){
return -1
}
let mid = parseInt((low + high ) /2);
if(key ===arr[mid]){
return mid
}else if(key> arr[mid]){
low = mid + 1;
return binarySearch(arr,key,low,high);
}else if(key < arr[mid]){
high = mid - 1;
return binarySearch(arr,key,low,high);
}
}
9、深度优先遍历(DFS)
DFS就是从图中的一个节点开始追溯,直到最后一个节点,然后回溯,继续追溯下一条路径,直到到达所有的节点,如此往复,直到没有路径为止。
10、广度优先遍历(BFS)
BFS从一个节点开始,尝试访问尽可能靠近它的目标节点。本质上这种遍历在图上是逐层移动的,首先检查最靠近第一个节点的层,再逐渐向下移动到离起始节点最远的层。
设计模式
1. 单例模式
function Singleton(){}
function getInstance(){
var instance = null;
return function(){
if(!instance){
instance = new Singleton()
}
return instance
}
}
2. 简单工厂模式
function CarFactory(color,price){
var car = new Object();
car.color = color;
car.price = price;
car.getPrice = function(){ return this.price; }
return car
}
var car1 = CarFactory('red',5000);
var car2 = CarFactory('white',7000);
3. 模块模式
var singleMode = (function(){
// 创建有变量
var privateNum = 122
// 创建私有方法
function privateFunc(){}
//创建公有方法
function publicMethod1(){}
function publicMethod2(){}
// 返回一个对象包含公有方法和属性
return {
publicMethod1:publicMethod1,
publicMethod2:publicMethod2
}
})();
4.观察者模式 & 发布-订阅模式
观察者模式 在软件设计中是一个对象,维护一个依赖列表,当任何状态发生改变自动通知它们。
const subject = {
observers: [],// 观察者
notify() { // 通知
this.observers.forEach(observer =>{
observer.update()
})
},
attach (observer) { //添加观察者
this.observers.push(observer)
}
}
const observer = {
update(){
// 观察的人提供的通知调用的函数
console.log('updated')
}
}
subject.attach(observer) // 加入观察
subject.notify()// 通知
发布-订阅模式,消息的发送方,叫做发布者(publishers),消息不会直接发送给特定的接收者,叫做订阅者。
const Event = (()=>{
const list = {};
let listen, trigger, remove;
listen = function(key,fn){
if(!list[key]){
// 未订阅此类消息,则给该类消息创建一个缓存列表
list[key] = [];
}
// 订阅消息添加到缓存列表
list[key].push(fn);
};
trigger = function(key,...params){
// 取出改消息对应的回调函数的集合
var fns = list[key];
// 如果没有订阅过该消息,则返回
if(!fns||fns.length === 0){
return false
}
for(let i = 0,l=fns.length; i<l; i++){
fns[i].apply(this, params);//params是发布消息的传递的参数
}
};
remove = function(key, fn){
// 如果key对应的消息没有订阅过的话,则返回
var fns = list[key];
if(!fns) {
return false;
}
// 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
if(!fn) {
fns && (fns.length = 0);
}else {
for(var i = 0, l = fns.length; i < l; i++){
var _fn = fns[i];
if(_fn === fn) {
fns.splice(i,1);// 删除订阅者的回调函数
}
}
}
}
return {
listen: listen,
trigger: trigger,
remove: remove
}
})();
// 测试代码如下:
Event.listen("eventName",function(param) {
console.log("params:"+param); // 打印出尺码为42
});
Event.trigger("eventName",'555');
5. 责任链模式
function order500(orderType,isPay,count){
if(orderType == 1 && isPay == true) {
console.log("亲爱的用户,您中奖了100元红包了");
}else {
//我不知道下一个节点是谁,反正把请求往后面传递
return "nextSuccessor";
}
};
function order200(orderType,isPay,count) {
if(orderType == 2 && isPay == true) {
console.log("亲爱的用户,您中奖了20元红包了");
}else {
//我不知道下一个节点是谁,反正把请求往后面传递
return "nextSuccessor";
}
};
function orderNormal(orderType,isPay,count){
// 普通用户来处理中奖信息
if(count > 0) {
console.log("亲爱的用户,您已抽到10元优惠卷");
}else {
console.log("亲爱的用户,请再接再厉哦");
}
}
// 下面需要编写职责链模式的封装构造函数方法
var Chain = function(fn){
this.fn = fn;
this.nextSuccessor = null;
}
Chain.prototype.setNextSuccessor = function(successor){
return this.successor = successor
}
// 把请求往下传递
Chain.prototype.passRequest = function(){
var ret = this.fn.apply(this,arguments);
if(ret === 'nextSuccessor'){
// 调用后面的节点
return this.successor && this.successor.passRequest.apply(this.successor, arguments);
}
return ret;
}
//现在我们把3个函数分别包装成职责链节点:
var chainOrder500 = new Chain(order500);
var chainOrder200 = new Chain(order200);
var chainOrderNormal = new Chain(orderNormal);
// 然后指定节点在职责链中的顺序
chainOrder500.setNextSuccessor(chainOrder200);
chainOrder200.setNextSuccessor(chainOrderNormal);
//最后把请求传递给第一个节点:
chainOrder500.passRequest(1,true,500); // 亲爱的用户,您中奖了100元红包了
chainOrder500.passRequest(2,true,500); // 亲爱的用户,您中奖了20元红包了
chainOrder500.passRequest(3,true,500); // 亲爱的用户,您已抽到10元优惠卷
chainOrder500.passRequest(1,false,0); // 亲爱的用户,请再接再厉哦
6. 策略模式
// 策略类
var obj = {
"A": function(salary) { return salary * 4; },
"B" : function(salary) { return salary * 3; },
"C" : function(salary) { return salary * 2; }
};
//环境类
var calculateBouns =function(level,salary) {
return obj[level](salary);
};
// 调用
console.log(calculateBouns('A',10000)); // 40000