【数据结构、算法与设计模式】前端要掌握的数据结构有哪些?

89 阅读5分钟

前端数据结构与算法基础知识学习笔记

数据结构

  • 队列
  • 链表(双向链表、循环链表)
  • 图 图是一组由边连接的节点(或顶点),任何二元关系都可以用图来表示。
  • 集合(一组无序唯一的数据组成,集合数据不能重复)
  • 字典 key:value的形式,像js中的对象

算法

1. 排序算法

十大经典排序算法的复杂度分析_排序算法时间复杂度-CSDN博客

image.png

1.1 冒泡排序

【排序算法】史上最通俗易懂的【冒泡排序】详解

冒泡排序虽然思路简单但是时间效率最差;时间复杂度是O(n^2)

外层循环是比较趟数,里层循环是每一趟比较的次数;

for(let i = 0; i < arr.length - 1; i++){
    for(let j = 0; j < arr.length - 1 - i; j++){
        if(arr[j] > arr[j+1]){
            [arr[j], arr[j+1]] = [arr[j+1], arr[j]]
        }
    }
}

1.2 选择排序

每一次都找到数据中的最小值然后按次序摆放;外面循环是比较趟数,里面循环是找到最小值,并将最小值放到有序数据后面。由于每次都要寻找最小的数据,因此时间复杂度永远为n^2

for(let i = 0; i < arr.length -1; i++){
    let min = i 
    for(let j = i; j < arr.length; j++){
        if(arr[min] > arr[j]){
            min = j
        } 
    if (min != i) {
        swap(arr[i], arr[min]);
    }
}

1.3 插入排序

用需要插入的数据和已经排好序的数据开始比较,看插入在哪个位置比较合适;

function insertionSort(arr) {
    var len = arr.length;
    var preIndex, current;
    for (var i = 1; i < len; i++) {
        preIndex = i - 1;
        current = arr[i];
        while(preIndex >= 0 && arr[preIndex] > current) {
            arr[preIndex+1] = arr[preIndex];
            preIndex--;
        }
        arr[preIndex+1] = current;
    }
    return arr;
}

1.4 归并排序

归并排序时间复杂度比前三个排序算法好,为O(nlogn) 类似于二分查找算法,分成单个小数组然后归并成大数组;

1.5 快速排序

快速排序(Quicksort)的Javascript实现 - 阮一峰的网络日志 (ruanyifeng.com)

快速排序时间复杂度为O(nlogn)

快速排序:
(1)在数据集之中,选择一个元素作为"基准"(pivot)。
(2)所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。
(3)对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。

var quickSort = function(arr) {
  if (arr.length <= 1) { return arr; }
  var pivotIndex = Math.floor(arr.length / 2);
  var pivot = arr.splice(pivotIndex, 1)[0];
  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));

};

2. 搜索算法

2.1 顺序搜索算法

顺序搜索算法就是最基本的按着顺序一个一个的与要查找的数据做比较;

2.2 二分查找算法

分为左右两个部分,比较mid中间数据与要查找的数据的大小,数据是已经排好序的,所以呢如果要查找的数据小于mid中间数据就在左半部分查找;时间复杂度O(logn)

设计模式

1. 单例模式

// 简单单例
const StudentMgr = (function () {
  let instance;
  let stu = [];

  function StudentMgr() {
    if (!instance) {
      instance = this;
    }
    return instance;
  }
  StudentMgr.prototype.add = function ({ id, name, age }) {
    stu.push({ id, name, age });
    console.log("add successful!");
  };
  StudentMgr.prototype.get = function (id) {
    return stu.filter((item) => item.id === id);
  };
  return StudentMgr;
})();

const mgr = new StudentMgr();
mgr.add({ id: "1", name: "x", age: 20 });
mgr.add({ id: "2", name: "y", age: 30 });

const stu = mgr.get("2");

const mgr2 = new StudentMgr();
const stu2 = mgr.get("2");
console.log(mgr === mgr2); //true

//ES6 class
class StudentMgr {
  stu = []
  constructor() {
    if (!StudentMgr.instance) {
      StudentMgr.instance = this;
    }
    return StudentMgr.instance;
  }

  add({ id, name, age }){
    this.stu.push({ id, name, age })
    console.log("class StudentMgr instance stu add successful!")
  }
  get(id){
    return this.stu.filter((item) => item.id === id)
  }
}

const mgr = new StudentMgr();
mgr.add({ id: "1", name: "x", age: 20 });
mgr.add({ id: "2", name: "y", age: 30 });
const student = mgr.get("2");
console.log(student)

const mgr2 = new StudentMgr();
mgr.add({ id: "1", name: "xx", age: 10 });
const student2 = mgr2.get("1");
console.log(student2)

2. 策略模式

策略模式(Strategy Pattern)是一种行为设计模式,允许在运行时选择算法或策略,并对这些策略进行封装。它使得算法可以在不影响客户端的情况下发生变化。

优点

  1. 符合开闭原则:易于扩展,可以轻松添加新的验证规则,而不需要修改现有代码。

  2. 消除条件语句:避免使用大量的条件分支语句,代码更加清晰。

  3. 代码复用:各个策略类可以在不同的上下文中重复使用。

  4. 灵活性:可以在运行时动态切换策略,满足不同的业务需求。

使用场景

当一个类定义了许多行为,并且这些行为在类的操作中以多个条件语句的形式出现时,可以使用策略模式。 表单验证、算法选择,支付方式选择等。

const strategies = {
    add: function (a, b) {
      return a + b
    }

    subtract: function (a, b) {
      return a - b
    } 

    multiply: function (a, b) {
      return a * b
    }

    divide: function (a, b) {
      return a / b
    }
}

使用策略模式,对输入内容进行校验

const strategies = {
    userName: function(userName){
        if (userName.length >= 6 && userName.length <= 12){
            return null
        } else {
            return "username length error!"
        }
    },
    password: function(password){
        if (password.length >= 6 && password.length <= 12){
            return null
        } else {
            return "password length error!"
        }
    },
    email: function(email){
        const regex = new RegExp('/\S+@\S+\.\S+/');
        if(regex.test(email)){
            return null
        } else {
            return "email error!"
        }
    }
}

class FormValidator {
    strategies = []
    constructor(strategies){
        this.strategies = strategies
    }
    validate(data){
        const errors = [];
        for(let key in this.strategies){
            if(!this.strategies[key]){
                throw new Error('Unexpect strategy Key:', key);
            }
            let error = this.strategies[key](data[key]);
            if(error){
                errors.push(error);
            }
        }
        return errors;
    }
}

const formValidator = new FormValidator(strategies);

const data = {
    userName: 'Hada',
    password: '782802U279dxnkla',
    email: '12345@',
};

const errors = formValidator.validate(data)
console.log(errors)

3. 订阅者模式

模仿Vue3的mitt实现简单的订阅发布

class Emitter {
    constructor(){
        this.events = new Map()
    }
    // 订阅事件
    on(type, handler){
        const handlers = this.events.get(type)
        if(!handlers){
            this.events.set(type, [handler])
        } else {
            handlers.push(handler)
        }
        console.log('订阅成功!')
    }
    //取消订阅
    off(type, handler){
        const handlers = this.events.get(type)
        if(handlers) {
            this.events.set(type, handlers.filter(h => h !== handler));
        }
        console.log('取消订阅成功!')
    }

    allClear(){

    }
    //发布事件
    emit(type, message){
        let handlers = this.events.get(type)
        if(handlers){
            handlers.forEach(function(handler) {  
                handler(message)   
            })
        }
        handlers = this.events.get('*')
        if(handlers){
            handlers.forEach((handler) => {
                handler(type, message)
            })
        }
    }
}

const emitter = new Emitter()
emitter.on('foo', e => console.log('foo', e))
emitter.on('foo', e => console.log('function2 foo', e))
emitter.on('go', e => console.log('go', e))
emitter.on('leave', e => console.log('leave', e))
emitter.off('leave', e => console.log('leave', e))
emitter.on('*', (type, e) => console.log('*', type, e))

emitter.emit('foo', {a: 'b'}) //发布消息