前端算法与数据结构之栈、队列、链表(一)

486 阅读2分钟

前端算法与数据结构

leetcode官方网址

1、算法和数据结构含义

  • 数据结构:计算机存储,组织数据的方式
  • 算法:一系列解决问题的清晰指令
1-1 关系
  • 程序 = 数据结构 + 算法
  • 数据结构为算法提供服务,算法围绕数据结构进行操作
1-2 数据结构
  • 栈、队列、链表(有序)
  • 集合、字典(无序)
  • 树、堆、图(有一定连接关系)
1-3 算法
  • 链表:遍历链表、删除节点链表
  • 树、图:深度/广度优先遍历
  • 数组:冒泡/选择/插入/快速排序等等

2、leetcode的介绍

2-1 功能
  • 题库、社区、竞赛、模拟刷题

1620272226001

1620272326589

  • 做题
    • 查看题目描述,评论,题解,提交记录
    • 1620272463634

3、时间复杂度

  • 一个函数,用大写O来表示,比如:O(1)、O(n)、O(n^2)...等等

图解

  • 时间复杂度为O(1)

    let i = 0;
    i = i + 1;
    
  • 时间复杂度为O(n)

    for(let i = 0; i < n; i++) {
    	console.log(i)
    }
    // 在一个for循环体里面,这个循环做了n次
    
  • 时间复杂度为:O(n) + O(1) = O(n)

let i = 0;
i = i + 1;
for(let i = 0; i < n; i++) {
	console.log(i)
}
  • 时间复杂度为:O(n) * O(n) = O(n^2)
for(let i = 0; i < n; i++) {
	for(let j = 0; j < n; j++) {
		console.log(i,j)
	}
}
  • 时间复杂度为:O(logN)
let i = 1;
while(i < n) {
    console.log(i)
    i *= 2;
}
// 如果a^x =N(a>0,且a≠1),那么数x叫做以a为底N的对数,记作x=logaN

4、空间复杂度

  • 一个函数,用大写O来表示,比如:O(1)、O(n)、O(n^2)...等等

  • 算法在运行过程中临时占用存储大小的量度。你写的代码占用的空间大小

  • 空间复杂度为O(1)

let i = 0;
i = i + 1;
  • 空间复杂度为O(n)
let arr = [];
for(let i = 0; i < n; i++) {
	arr.push(i);
}
  • 空间复杂度为:O(n^2)
let arr = [];
for(let i = 0; i < n; i++) {
	arr.push([]);
    for(let j = 0; j < n; j++) {
        arr[i].push(j);
    }
}
// 矩阵,本质二维数组

5、栈

5-1栈简介
  • 栈是一个后进先出的数据结构

1620283277523

  • JavaScript中没有栈,可以用Array数组实现栈的所有功能
    • 代码实现,定义一个stack的空数组,在push方法打上断点,按F5,编辑器自带的js调试,点击箭头执行下一步操作,左边控制面板可以看出:先入栈的元素是‘a’,先出栈的元素是'd',满足后进先出原则。
  • 栈常用操作:push、pop、stack[stack.length - 1]

1620298107492

1620298169848

5-2栈应用
  • 十进制转二进制

    • 倒叙写二进制

    1620289310238

  • 判断字符串的括号是否有效

    • 越靠后的左括号,对应的右括号越靠前。
    • 从左到右遍历括号,遇到左括号入栈,遇到右括号出栈,最后栈空了就是合法的。

    1620289355548

  • 函数调用堆栈

    • 最后调用的函数,最先执行完。
    • JS解释器使用栈来控制函数的调用顺序
function run() {
    console.log('1')
    sayHello();
    console.log('2')
}
function sayHello(){
    console.log('hello')
}
run();
// 1
// hello
// 2
5-3栈算法题解析
  • 20. 有效的括号

  • 对于没有闭合的左括号而言,越靠后的左括号,对应的右括号就越靠前

  • 满足后进先出,考虑用栈解决。

  • 解题步骤

    • 1、新建一个栈
    • 2、循环字符串,遇到左括号入栈,遇到右括号出栈,遇到类型不匹配判定为不合法。
    • 3、最后栈空了就合法
    var isValid = function (s) {
      const stack = []; // 新建一个栈
      if (s.length % 2 == 1) {
        return false // 奇数的长度直接不合法
      }
      for (let i = 0; i < s.length; i++) {
        const val = s[i]
        if (val === '(' || val === '{' || val === '[') {
          stack.push(val) // 判断左括号合法,入栈
        } else {
          const top = stack[stack.length - 1]; // 定义栈顶元素
          if (
            // 栈顶元素的栈底元素匹配
            (top === '(' && val === ')') || (top === '{' && val === '}') || (top === '[' && val === ']')
          ) {
            stack.pop(); // 出栈
          } else {
            return false
          }
        }
      }
      return stack.length === 0; // 栈空了就合法
    };
    isValid("()");
    isValid("()[]{}");
    isValid("(]");
    isValid("([)]");
    
5-4前端与栈
  • 函数的调用(按循序去执行函数)
const fn1 = () => {
  fn2();
}
const fn2 = () => {
  fn3();
}
const fn3 = () => {}
fn1();  
// 最先执行的函数是fn1 -> fn2 -> fn3 
  • 执行入栈

1620293610769

  • 执行出栈

    1620293713165

6、队列

6-1队列简介
  • 队列是先进先出的数据结构

    1620294367021

  • JavaScript中没有队列,可以用Array数组实现队列的所有功能

  • 入队和出队

  • 队列常用操作:push、shift、queue[0]

    1620295210652

1620295280606

6-2队列应用
  • 排队打饭
  • JS异步中的任务队列
    • JS是单线程的,无法同时处理异步中的并发任务。
    • 使用队列任务先后处理异步任务
  • 计算最近请求次数
6-3队列算法题解析
  • 933. 最近的请求次数

  • 解题步骤

    • 有新情求就入队,3000ms前发出的请求出队
    • 队列的长度就是最近的请求次数
    // 在这个时间 的 过去 3 秒 内, 不停的请求, 求在这个3秒内这个请求了多少下
    var RecentCounter = function() {
        this.queue = [];
    };
    
    /** 
     * @param {number} t
     * @return {number}
     */
    RecentCounter.prototype.ping = function(t) {
        this.queue.push(t)
        // 队头在 t - 3000ms内,出队
        while(this.queue[0] < t - 3000) {
            this.queue.shift()
        }
        return this.queue.length;
    };
    // 时间复杂度:O(n) , 空间复杂度O(n)
    
6-4前端与队列
  • Javascript语言的执行环境是单线程的。js异步原理解析

    所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。

1620300436833

7、链表

7-1链表简介
  • 多个元素组成的列表
  • 元素存储不连续,用next指针连在一起

1620301104532

  • JS没有链表
  • 可以用Object模拟链表

1620301659309

  • 遍历链表

    var a = {
      val: 'a'
    }
    var b = {
      val: 'b'
    }
    var c = {
      val: 'c'
    }
    var d = {
      val: 'd'
    }
    
    a.next = b;
    b.next = c
    c.next = d;
    // 遍历链表
    
    let p = a;
    while(p) {
      console.log(p.val)
      p = p.next;
    }
    

1620301903085

  • 链表插入

    1620302297070

    • 链表删除
    // 删除
    c.next = d; // 把c的next指向d,就能删除e
    
7-2链表算法题解析
  • 237. 删除链表中的节点

  • 解题思路

    • 无法直接获取被删除节点的上一个元素,将被删除节点的值改成下个节点的值
    • 将被删除节点转移到下个节点(如果想要删除节点5,先把5的节点指向下个元素值,此时链表为4-1-1-9,再把节点1下个元素删除,即可得到4-1-9的链表)
    /**
     * Definition for singly-linked list.
     * function ListNode(val) {
     *     this.val = val;
     *     this.next = null;
     * }
     */
    /**
     * @param {ListNode} node
     * @return {void} Do not return anything, modify node in-place instead.
     */
    var deleteNode = function(node) { node = 5
        node.val = node.next.val;
        node.next = node.next.next;
    };
    
7-4前端与链表
7-4-1 JS原型链含义
  • 原型链的本质是链表
  • 原型链上的节点是各个原型对象,比如Function.prototypeObject.prototype
  • 原型链通过__proto__属性连接各种原型对象
7-4-2 原型链
obj -> Object.prototype -> null
fun -> Function.prototype -> Object.prototype -> null
arr -> Array.prototype -> Object.prototype -> null

1620354676155

7-4-3 原型链知识点
  • 如果A沿着原型链能找到B.prototype,那么A instanceof B 为true
const obj = {}
const fun = ()=> {}
const arr = []
// 对于数组来说,arr即是Array的实例,也是Object的实例
arr instanceof Array // true
arr instanceof Object // true
fun instanceof Function // true
fun instanceof Object // true
obj instanceof Object // true
  • 如果在A对象上没有找到X属性,那么会沿着原型链找到X的属性
const obj = {}
obj.name = undefined
Object.prototype.name = "张三"
obj.name = "张三"
// 函数原型链可以指向Object.prototype
Object.prototype.age = "16"
const fun1 = ()=> {}
fun1.age = 16

1620357599056

1620358057065