数据结构与算法---JS与栈

news2024/11/16 8:58:46

前言

js里,是没有栈这种原生的数据结构。但是我们可以通过自定义创建栈类,来实现对添加/删除元素时更多的控制。

创建栈类

// 初始化一个基于数组的栈类
class Stack {
  constructor() {
    this.items = [];
  }
}

为什么我们要选择数组作为栈类的存储数据类型?

因为栈是一种遵从后进先出(LIFO)原则的有序集合。新添加或待删除的元素都保存在栈的同一端,称为栈顶,另外一端为栈底。在栈里,新元素越靠近栈顶,旧元素越靠近栈底。

栈的用途很广。被用于编程语言的编译器和内存中保存变量、方法调用等。也用于浏览器历史记录(浏览器的前进后退)

对栈的设想

由于栈遵循LIFO原则,不像数组可以随意对数组内的元素进行操作。所以需要对元素的插入和删除等功能做限制。接下来,我们为栈类设想一些方法。

  • 添加一个(或几个)新元素到栈顶

push(ele) {
    this.items.push(ele)
}
  • 移除栈顶的元素,同时返回被移除的元素

pop() {
    return this.items.pop();
}
  • 返回栈顶的元素,不对栈做任何修改

peek() {
    return this.items[this.items.length-1]
}
  • 如果栈为空则返回true 否则返回false

isEmpty()  {
    return this.items.length === 0;
}
  • 移除栈内所有元素

clear() {
    this.items = [];
}
  • 返回栈里的元素个数

size() {
    return this.items.length
}

实例化Stack类:

const stack = new Stack();
stack.isEmpty(); // true
stack.push(5);
stack.push(6);
stack.peek(); // 6

创建一个基于JS对象的Stack类

作用

我们上面创建的Stack类,是使用一个数组来存储数据。那么我们现在为什么要用JS对象来替换Stack类的存储方式?

因为在使用数组时,大部分方法的时间复杂度为O(n)。n代表数组长度。即在最坏的情况下,我们需要迭代整个数组直到找到我们要找的那个元素。

另外数组是元素的一个有序集合,为了保证元素排列有序,它需要占用更多的内存空间。

在大多数编程语言里,能够直接获取元素,占用较少的内存空间的存储方式,无非是键值表了。

所以我们要使用一个JS对象来存储所有的栈元素,并保证他们的顺序并且遵循LFO原则。

声明一个Stack类

class Stack {
    constructor() {
        // count是预先索引,可以代表长度,但是不存在索引为count的元素
        this.count = 0;
        this.items = {};
    }
}

为什么要定义count属性?为了记录栈的大小,并帮助我们添加或删除元素。

向栈中插入元素

要向栈中添加元素,我们将使用count变量作为items对象的键名,插入的元素则是它的值。在往栈插入元素后,我们递增count变量。

push(ele) {
    this.items[this.count] = ele;
    this.count ++;
}

实例化栈

const stack = new Stack();
stack.push(5)
stack.push(8)
stack; // {count: 2,items:{0:5,1:8} }

判断是否空栈和它的大小

size() {return this.count}
isEmpty() {return this.count === 0}

从栈中弹出元素(从弹夹弹出最顶上的一颗子弹)

pop() {
    // 空栈,弹出undefined
    if (this.isEmpty()) {
        return undefined;
    }
    this.count --;
    const result = this.items[this.count]
    delete this.items[this.count]
    return result
}

查看栈顶的值

peek(){
 if (this.isEmpty()) {
    return undefined
  }
  return this.items[this.count - 1]
}

清空栈

方法①一键还原

clear() {
    this.count = 0;
    this.items = {};
}

方法②LIFO原则,循环出栈

clear() {
    while (!this.isEmpty()) {
        this.pop();
    }
}

toString

当我们使用数组作为数据结构的时候,不需要关心toString的实现、而对象需要我们自己去定义。

var a =['a','b',5]
a.toString() // 'a,b,5'
toString() {
    if (this.isEmpty()) {return ''};
    let objString = '';
    for (let i =0;i<this.count;i++) {
        objString+=(`[键名:${i}键值:${this.items[i]}]`)
    }
    return objString
}

时间复杂度

除了toString方法,我们创建的其他方法的复杂度均为O(1);代表我们可以直接找到目标元素并对其操作.

保护JS栈类内部的数据结构

在栈设计中,我们希望保护内部的元素。只有我们暴露出的方法才能修改内部结构。

比如在栈中,我们要确保元素只能被添加到栈顶。而不是任意位置。

也就是说,我们需要保护Stack类中声明的items和count属性。

上面的例子中,无论是以数组或者对象作为存储的数据类型。items和count都是Stack类暴露出来的公开属性。

破坏Stack类

为了方便理解,我们以数组作为Stack类的存储数据类型。

const stack = new Stack();
stack.push(5);
console.log(Object.getOwnPropertyNames(stack)); // ['items']
// 或者
console.log(Object.keys(stack)) // ['items']
// 直接破坏数据属性
stack['items'] = [1,2,3];

我们上面创建栈类是使用了ES6语法,是基于原型的类创建。这样做的好处在于能够节省内存空间并在扩展方面优于基于函数的类。但是这种方式不能声明私有属性的方法(直到ES11!)

保护内部属性-下划线命名约定

一部分开发者喜欢在js中使用下划线命名来约定这个属性为私有属性

class Stack {
  constructor() {
    this._items = {};
    this._count = 0;
  }   
}

但这只是为了规范开发时对类属性的保护,实际上并没有用处。

保护内部属性 - ES6的限定作用域Symbol

出发的思路点就是为了躲避Object.getOwnPropertyNames和Object.keys对stack实例的属性扫描。

Symbol是ES6新增的一种基本数据类型。它是不可变的。可以用作对象属性。

const _ItemKey = Symbol('dataStack')
class Stack {
    constructor() {
        this[_ItemKey] = []
    }
}
var stack1 = new Stack()
Object.keys(stack1) // []
Object.getOwnPropertyNames(stack1) // []

是不是找不到内部的_ItemKey属性了?

当你以为这样就可以成功躲避对象的属性扫描,那你就too young to simple啦

ES6新增的Object.getOwnProperty-Symbols方法能够拿到类里面声明的所有Symbols属性

Object.getOwnPropertySymbols(stack1) // [Symbol(stack01_item)]
stack1[Object.getOwnPropertySymbols(stack1)[0]] = [1,2,3]

保护内部属性 - ES6的WeakMap

有一种数据类型可以确保属性是私有的,这就是WeakMap。WeakMap以键值对存储数据。

const db = new WeakMap()
class Stack {
    constructor() {
        db.set(this,[])
    }
    push(t) {
        const items = db.get(this);
        items.push(t)
    }
    pop() {
        const items = db.get(this);
        return items.pop()
    }
}
const stack2 = new Stack()
stack2.push(5)
stack2.push(10)
stack2 // {}
stack2.pop() // 10
Object.keys(stack2) // []
Object.getOwnPropertyNames(stack2) // []

this是吧Stack类自己的引用作为键,存入WeakMap数据表里。

这样子确实是无懈可击了,Stack类里终于有了自己的私有属性。但是采用这种方法,会让代码变得难以理解,并且在继承扩展类时无法继承私有属性!

所以不推荐!

保护内部属性 - ES11的#变量

ES11(ES2020)在类中新增私有变量控制符#,在内部变量或者函数前添加一个hash符号#,可以将它们设置为私有属性,只能在类的内部可以使用。

class Stack {
    #items = {};
    #count = 0
    push(t) {
        this.#items[this.#count] = t
        this.#count ++;
    }
}
const stack3 = new Stack()
stack3.push('ddd')
stack3.items; // undefined
stack3.#items // Uncaught SyntaxError: Private field '#items' must be declared in an enclosing class
Object.keys(stack3) // []

用栈解决问题

栈可以解决很多计算机科学问题。

从十进制到二进制

在日常生活中,我们主要使用十进制,在计算机科学中,二进制非常重要。因为计算机的所有内容都是用二进制数字表示的(0和1)

我们要利用栈来强化JS把十进制转换成二进制的能力。

规则:

要把十进制转成二进制。我们可以用十进制数除以2(二进制是满二进一)并对商取整。直到结果是0为止。

我们现在把10转成二进制数字:

const decimalToBinary = (decNumber) => {
    const remStack = new Stack();
    let binaryString = '';
    // 避免方法改变传入的十进制值
    let cloneDecNumber = decNumber;
    while (cloneDecNumber > 0) {
        // 把余数存进去
        remStack.push(cloneDecNumber % 2);
        cloneDecNumber= Math.floor(cloneDecNumber /2);
    }
    // 取值
    while (!remStack.isEmpty()) {
     binaryString += remStack.pop().toString();
    }
    return binaryString
}
decimalToBinary(233) // '11101001'
decimalToBinary(10) // '1010'
decimalToBinary(1000) // '1111101000'
decimalToBinary(100) // '1100100'

进制转换算法

我们修改之前的算法。使它能够把十进制转换成基数为2-6的任意进制。

const baseConverter = (decNumber,base) => {
    const remStack = new Stack();
    let number = decNumber;
    let rem;
    let baseString = '';
    if (base <2 || base>36) {return ''}
    while (number > 0) {
        remStack.push(Math.floor(number % base))
        number = Math.floor(number / base)
    }
    // digits是参照表
    const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    while (!remStack.isEmpty()) {
        baseString += digits[remStack.pop()];
    }
    return baseString
}

十进制转二进制,余数是0或1

十进制转为八进制,余数是0-7

十进制转十六进制时,余数是0-15(16-1),但是计算机语言里没有所谓的15,而是用A、B、C、D、E、F分别对应10、11、12、13、14、15

因此我们需要对栈中的数字做转化

从十一进制起,字母表中的每个字母代表对应的基数。

baseConverter (100345,2)
//'11000011111111001'
baseConverter (100345,8)
//'303771'
baseConverter (100345,35)
//'2BW0'
baseConverter (100345,16)
//'187F9'

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/399380.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

23.3.9打卡 AtCoder Beginner Contest 259

A题 题解 对于x特判一下就好 代码 void solve() {ll x,d;cin>>n>>m>>x>>t>>d;if(n>m){nmin(n,x);if(n<m){cout<<t;return;}cout<<(m-n)*dt;}else{mmin(m,x);cout<<(m-n)*dt;}return; }B 三角函数全还给高中老师了 题…

ARM 学习(一)

ARM 处理器的运行模式ARM处理器共有7种运行模式&#xff0c;如下表所示&#xff1a;处理器模式描述用户模式&#xff08;User&#xff09;正常程序运行模式中断模式&#xff08;IRQ&#xff09;用于通常的中断处理快速中断模式&#xff08;FIQ&#xff09;用于高速传输和通道处…

qt控件增加渐变色效果

ui->returnBtn->setStyleSheet("color: rgb(0, 0, 0);""background:qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, ""stop:0 #5f5f5f, stop:0.5 #ffffff, stop:0.98 #5f5f5f);""border:none;");效果如下图&#xff1a; …

java-3月xly笔记

时间安排&#xff1a; 朱祥祥 会议号&#xff1a;417 523 3263 服务器&#xff1a;doc.canglaoshi.org jdk下载&#xff1a;https://repo.huaweicloud.com/java/jdk/ jdk配置环境变量 检测命令&#xff1a; &#xff08;1&#xff09;winr&#xff0c;输入cmd&#xff0c…

ActiveMQ反序列化漏洞原理+复现

ActiveMQ反序列化漏洞 ActiveMQ ActiveMQ是开源消息总线&#xff0c;消息中间件 工作原理 通过使用消息队列&#xff0c;实现服务的异步处理&#xff0c;主要目的是减少请求响应时间和解耦合。 消息队列&#xff0c;服务器A将客户发起的请求放入服务器B的消息队列中&#…

裸辞两个月还能不能找到工作?亲身经历告诉你结果·····

这是我在某论坛看到的一名网友的吐槽&#xff1a; 软件测试四年&#xff0c;主要是手动测试&#xff08;部分自动化测试和性能测试&#xff0c;但是用的是公司内部自动化工具&#xff0c;而且我自动化方面是弱项。&#xff09;现在裸辞两个月了&#xff0c;面试机会少而且面试…

树与二叉树(二叉树的表示,性质,遍历,还原)

基本术语&#xff1a;A&#xff08;或B&#xff09;是I的祖先&#xff0c;I是A&#xff08;或B&#xff09;的子孙&#xff1b;D是I的双亲&#xff0c;I是D的孩子&#xff1b;节点的孩子个数称为节点的度&#xff1b;树中节点的最大度数称为树的度&#xff1b;度大于0的节点称为…

Git 命令行5步解决冲突方法(亲测有效)

总体步骤如下&#xff1a; git pull --rebase 解决冲突文件 file1.c。git add file1.cgit commit -m "*****" git pushgit rebase --continue &#xff0c;此时冲突消失强推&#xff0c;git push origin xxxx -f 本人解决的例子如下&#xff1a; 第一步、拉取…

SSH框架 (一) SpringMVC

1.SpringMVC 1.1 概述 Spring MVC 是 Spring 提供的一个基于 MVC 设计模式 的轻量级 Web 开发框架&#xff0c;本质上相当于 Servlet相当于对其的进一步封装 核心组件 DispatcherServlet。 Spring MVC框架 内部采用松耦合、可插拔的组件结构&#xff0c; 具有高度可配置性&am…

1/4车、1/2车、整车悬架H2/H∞控制仿真合集

目录 前言 1. 1/4悬架系统 1.1数学模型 1.2 H2/H∞求解反馈阵阵 1.3仿真分析 2. 1/2悬架系统 2.1数学模型 2.2 H2/H∞求解反馈阵阵 2.3仿真分析 3. 整车悬架系统 3.1数学模型 整车7自由度主动悬架数学模型 3.2 H2/H∞求解反馈阵阵 3.3仿真分析 4.总结 参考文献 …

Java-Web之s2-001与CommonsCollections

本文源自我个人入坑Java-Web安全的一点小经验&#xff0c;献给那些看得懂java代码但不知道从哪里入手代审的师傅们&#xff1a;&#xff09; Struts2之s2-001 环境配置 说说环境配置的问题&#xff0c;大多数人对漏洞复现的恐惧感还是来自于环境的配置&#xff0c;也许配了大…

在 Ubuntu 下编写 C++

在 Ubuntu 下编写 C 在 Ubuntu 上面编写 C&#xff0c;本章节内容主要介绍在 Ubuntu 在终端窗口下使用 vi/vim 编辑一 个 C源文件。通过编写最简单的示例“Hello,World&#xff01;”。带领大家学习如何在 Ubuntu 终端下编 辑和编译 C。这里要求大家会在 Ubuntu 上使用 vi/vim…

图片动画化应用中的动作分解方法

作者 | FesianXu 前言 最近基于AI的换脸应用非常的火爆&#xff0c;同时也引起了新一轮的网络伦理大讨论。如果光从技术的角度看&#xff0c;对于视频中的人体动作信息&#xff0c;通常可以通过泰勒展开分解成零阶运动信息与一阶运动信息&#xff0c;如文献[1,2]中提到的&…

Multisim 14.3 安装教程

1、首先解压Multisim 安装包。 2、解压完成后&#xff0c;双击点进去&#xff0c;找到setup这个文件&#xff0c;点进去。 3、找到setup文件里面的install.exe文件&#xff0c;并鼠标右键 “找到以管理员身份运行”。 4、选择“我接受上述许可协议”&#xff0c;点击下一步。 …

InceptionTime 复现

下载数据集&#xff1a; https://www.cs.ucr.edu/~eamonn/time_series_data/ 挂梯子&#xff0c;开全局模式即可 配置环境 虚拟环境基于python3.9&#xff0c; tensorflow下载&#xff1a;pip install tensorflow&#xff0c;不需要tensorflow-gpu&#xff08;高版本python&…

电脑怎么既录屏又录人脸?分享2个宝藏方法,轻松学会

在如今的数字时代&#xff0c;视频已成为人们表达和传递信息的最佳方式。无论是用于工作、教育、演示还是娱乐&#xff0c;录制高质量的视频已成为不可或缺的需求。然而&#xff0c;有些场景需要同时录制屏幕和人脸&#xff0c;电脑怎么既录屏又录人脸&#xff1f;本文将分享两…

Unity入门精要03---透明效果

本节知识架构 1.渲染顺序与渲染队列 如果采用了透明度混合即要是实现半透明效果&#xff0c;那么就要关闭深度写入&#xff0c;那么此时渲染顺序就会变得非常非常重要&#xff0c;不然会出现不正确的遮挡效果。具体的分析可见书中解释 一句话概括就是因为没有写入深度&#xf…

【3.9】RedisAOF日志、字符串、操作系统进程管理

4. 进程管理 进程、线程基础知识 什么是进程 我们编写的代码只是一个存储在硬盘的静态文件&#xff0c;通过编译后就会生成二进制可执行文件&#xff0c;当我们运行这个可执行文件后&#xff0c;它会被装载到内存中&#xff0c;接着 CPU 会执行程序中的每一条指令&#xff0c;…

ubuntu 使用 CMake 构建 Qt5 项目

Qt 概述 概念 Qt 是一个跨平台的 C 图形用户界面应用程序框架 常见的 C GUI: Qt 和 MFC 跨平台 Windows Linux MacOS 嵌入式平台 版本 包括商业版和开源免费版 案例 Linux 桌面环境 KDE WPS Office Qt 安装 下载地址: https://download.qt.io/archive/qt/ http…

37.Java进阶之实现动态编译

文章目录1. 作为程序员的最高追求2.如何实现动态编译2.1 生成源码2.2 调用编译器API对Test源码文件进行编译生成字节码2.3 调用类加载器对字节码进行加载得到Class对象2.4 使用Class对象创建对象进行使用3. Java编译API学习4. 类加载机制4.1 类加载过程4.2 类加载器的层次结构4…