JavaScript 数据结构与算法(一) 数组、栈、队列、链表、集合和字典

本文参考文献:https://www.cnblogs.com/AhuntSun-blog/p/12636718.html配套视频教程:https://www.bilibili.com/video/BV1r7411n7Pw?p=1&spm_id_from=pageDriver目录数据结构栈 Stack前置知识-执行上下文(函数执行栈/函数调用栈)简介常见例题封装和实现案例:十进制转二进制队列 Queue简介封装和实现案例:击鼓传花优先队列简介封装和实现arr.splice() & ar.
摘要由CSDN通过智能技术生成

本文参考文献:https://www.cnblogs.com/AhuntSun-blog/p/12636718.html
配套视频教程:https://www.bilibili.com/video/BV1r7411n7Pw?p=1&spm_id_from=pageDriver

数据结构-一

栈 Stack

前置知识-执行上下文(函数执行栈/函数调用栈)

当一段 JavaScript 代码在运行的时候,它实际上是运行在执行上下文中。下面3种类型的代码会创建一个新的执行上下文:

  • 全局上下文是为运行代码主体而创建的执行上下文,也就是说它是为那些存在于JavaScript 函数之外的任何代码而创建的。
  • 每个函数会在执行的时候创建自己的执行上下文。这个上下文就是通常说的 “本地上下文”。
  • 使用 eval() 函数也会创建一个新的执行上下文。

每一个上下文在本质上都是一种作用域层级。每个代码段开始执行的时候都会创建一个新的上下文来运行它,并且在代码退出的时候销毁掉。看看下面这段 JavaScript 程序:

let outputElem = document.getElementById("output");

let userLanguages = {
   
  "Mike": "en",
  "Teresa": "es"
};

function greetUser(user) {
   
  function localGreeting(user) {
   
    let greeting;
    let language = userLanguages[user];

    switch(language) {
   
      case "es":
        greeting = `¡Hola, ${
     user}!`;
        break;
      case "en":
      default:
        greeting = `Hello, ${
     user}!`;
        break;
    }
    return greeting;
  }
  outputElem.innerHTML += localGreeting(user) + "<br>\r";
}

greetUser("Mike");
greetUser("Teresa");
greetUser("Veronica");

这段程序代码包含了三个执行上下文,其中有些会在程序运行的过程中多次创建和销毁。每个上下文创建的时候会被推入执行上下文栈。当退出的时候,它会从上下文栈中移除。

  • 程序开始运行时,全局上下文就会被创建好。
    • 当执行到 greetUser("Mike") 的时候会为 greetUser() 函数创建一个它的上下文。这个执行上下文会被推入执行上下文栈中。
      • greetUser() 调用 localGreeting()的时候会为该方法创建一个新的上下文。并且在 localGreeting() 退出的时候它的上下文也会从执行栈中弹出并销毁。 程序会从栈中获取下一个上下文并恢复执行, 也就是从 greetUser() 剩下的部分开始执行。
      • greetUser() 执行完毕并退出,其上下文也从栈中弹出并销毁。
    • greetUser("Teresa") 开始执行时,程序又会为它创建一个上下文并推入栈顶。
      • greetUser() 调用 localGreeting()的时候另一个上下文被创建并用于运行该函数。 当 localGreeting() 退出的时候它的上下文也从栈中弹出并销毁。 greetUser() 得到恢复并继续执行剩下的部分。
      • greetUser() 执行完毕并退出,其上下文也从栈中弹出并销毁。
    • 然后执行到 greetUser("Veronica") 又再为它创建一个上下文并推入栈顶。
      • greetUser() 调用 localGreeting()的时候,另一个上下文被创建用于执行该函数。当 localGreeting()执行完毕,它的上下文也从栈中弹出并销毁。
      • greetUser() 执行完毕退出,其上下文也从栈中弹出并销毁。
  • 主程序退出,全局执行上下文从执行栈中弹出。此时栈中所有的上下文都已经弹出,程序执行完毕。

以这种方式来使用执行上下文,使得每个程序和函数都能够拥有自己的变量和其他对象。每个上下文还能够额外的跟踪程序中下一行需要执行的代码以及一些对上下文非常重要的信息。以这种方式来使用上下文和上下文栈,使得我们可以对程序运行的一些基础部分进行管理,包括局部和全局变量、函数的调用与返回等。

关于递归函数——即多次调用自身的函数,需要特别注意:每次递归调用自身都会创建一个新的上下文。这使得 JavaScript 运行时能够追踪递归的层级以及从递归中得到的返回值,但这也意味着每次递归都会消耗内存来创建新的上下文。

简介

数组是一个线性结构,并且可以在数组的任意位置插入、删除元素。而栈和队列就是比较常见的受限的线性结构,如下图所示:
在这里插入图片描述
栈的特点为先进后出,后进先出(LIFO:last in first out)。

程序中的栈结构

  • 函数调用栈A(B(C(D()))) 该语句中,函数A中调用函数B,函数B中调用函数C,函数C中调用函数D。具体的栈的变化就是:在A执行的过程中会将A压入栈,随后B执行时B也被压入栈,函数C和D执行时也会被压入栈。所以当前栈的顺序为:A->B->C->D(栈顶);函数D执行完之后,会弹出栈被释放,弹出栈的顺序为D->C->B->A;
  • 递归:为什么没有停止条件限制的递归会导致栈溢出 Stack Overfloat错误?假设函数A为递归函数,在函数体内不断地调用自己,也就不断的在栈中压入新的函数上下文(因为函数还没有执行完,不会把函数弹出栈),最后就会造成栈溢出。
常见例题

题目:有6个元素6,5,4,3,2,1按顺序进栈,问下列哪一个不是合法的出栈顺序?

  • A:5 4 3 6 1 2 (√)
  • B:4 5 3 2 1 6 (√)
  • C:3 4 6 5 2 1 (×)
  • D:2 3 4 1 5 6 (√)

题目所说的按顺序进栈指的不是一次性全部进栈,而是可以有进有出,但是进栈顺序必须为6 -> 5 -> 4 -> 3 -> 2 -> 1。

解析:

  • A答案:65进栈,5出栈,4进栈出栈,3进栈出栈,6出栈,21进栈,1出栈,2出栈(整体入栈顺序符合654321);
  • B答案:654进栈,4出栈,5出栈,3进栈出栈,2进栈出栈,1进栈出栈,6出栈(整体的入栈顺序符合654321);
  • C答案:6543进栈,3出栈,4出栈,之后应该5出栈而不是6,所以错误;
  • D答案:65432进栈,2出栈,3出栈,4出栈,1进栈出栈,5出栈,6出栈。符合入栈顺序;
封装和实现

栈可以基于数组或链表实现。

实现目标:
封装一个栈类,可实现栈的结构,并能够进行六种常见的栈操作:

  • push(element):添加一个新元素到栈顶位置;
  • pop():移除栈顶的元素,同时返回被移除的元素;
  • peek():返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶的元素,仅仅返回它);
  • isEmpty():如果栈里没有任何元素就返回true,否则返回false;
  • size():返回栈里的元素个数。这个方法和数组的length属性类似;
  • toString():将栈结构的内容以大字符串的形式返回。

基于数组的实现:

class Stack {
   
    constructor() {
   
        this.items = []
    }
    push(element) {
   
        this.items.push(element)
    }
    pop() {
   
        return this.items.pop()
    }
    peek() {
   
        return this.items[this.items.length - 1]
    }
    isEmpty() {
   
        if (this.items.length === 0) {
   
            return true
        } else {
   
            return false
        }
    }
    size() {
   
        return this.items.length
    }
    toString() {
   
        return this.items.join(' ')
    }
}

使用方式:

let  s = new Stack()
s.push(20)
s.push(10)
s.push(100)
s.push(77)
console.log(s)		// 20,10,100,77

console.log(s.pop());
console.log(s.pop());

console.log(s.peek());		// 10
console.log(s.isEmpty());		// false

console.log(s.size());		// 2
console.log(s.toString());	// '20 10'
案例:十进制转二进制

我们知道,十进制转二进制需要对数进行除二取余,完成后再从下往上讲余数拼凑成二进制结果,这一方式就可以利用栈结构的特点来实现。

function d2b(val) {
   
	// 定义一个栈,保存余数
    let s = new Stack();
	// 进行循环的除法
    while (val > 0) {
   
    	// 保存余数
        s.push(val % 2);
        // 修改被除数
        val = Math.floor(val / 2);
    }
    let res = '';
    // 拼装结果
    while (!s.isEmpty()) {
   
        res += s.pop()
    }
    return res
}

队列 Queue

简介

队列也是一种受限的线性表,它的特点为先进先出(FIFO:First In First Out)。

  • 它只允许在队列的前端front进行删除操作
  • 只运行在队列的后端rear进行插入操作
    在这里插入图片描述
封装和实现

队列可以基于数组或链表实现。

实现目标:
封装一个队列的类,可实现队列的结构,并能够进行六种常见的队列操作:

  • enqueue(element):向队列尾部添加一个(或多个)新的项;
  • dequeue():移除队列的第一(即排在队列最前面的)项,并返回被移除的元素;
  • front():返回队列中的第一个元素——最先被添加,也将是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息与Stack类的peek方法非常类似);
  • isEmpty():如果队列中不包含任何元素,返回true,否则返回false;
  • size():返回队列包含的元素个数,与数组的length属性类似;
  • toString():将队列中的内容,转成字符串形式;

基于数组的实现:

class Queue {
   
    constructor() {
   
        this.items = []
    }
    enqueue(element) {
   
        this.items.push(element)
    }
    dequeue() {
   
        return this.items.shift()
    }
    front() {
   
        return this.items[0]
    }
    isEmpty() {
   
        if (this.items.length === 0) {
   
            return true
        } else {
   
            return false
        }
    }
    size() {
   
        return this.items.length
    }
    toString() {
   
        return this.items.join(' ')
    }
}
案例:击鼓传花

需实现的功能:
传入:一个数组与一个数组num
循环遍历这个数组num次,第num次遍历到的元素从数组中剔除,并从下一个元素开始再次遍历,以此类推直至数组中剩下一个元素,打印这个元素及其在原数组中的索引位置。

function jgch(arr, num) {
   
    let que = new Queue();

    for (let i in arr) {
   
        que.enqueue(arr[i])
    }

    while (que.size() !== 1) {
   
        for 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值