两种类似数组的数据结构,在添加和删除元素时更为可控,他们就是队列

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

被用在编程语言的编译器和内存中保存变量、方法调用等,也被用于浏览器历史记录(浏览器的返回按钮)。

创建一个基于数组的栈

创建一个类来表示栈,利用数组来保存栈里的元素

class Stack {constructor() {this.items = []}
}

数组允许我们在任何位置添加或删除元素,由于栈遵循 LIFO 原则,所以需要对元素的添加和删除做一些限制,接下来为栈声明一些方法

  • push() : 添加新元素到栈顶
  • pop() : 移除栈顶的 元素,同时返回被移除的元素
  • peek() : 返回栈顶的元素,不对栈做任何修改
  • isEmpty() : 如果栈里没有任何元素返回 true, 否则返回 false
  • clear() : 移除栈里所有的元素
  • size() : 返回栈里的元素个数

向栈添加元素,首先实现 push() ,向栈里添加新元素,该方法只添加元素到栈顶,可以这样写

push(element) {this.items.push(element)
}

从栈移除元素,实现 pop() 方法,移除栈里的元素,栈遵循 LIFO 原则,移除的是最后添加进去的元素

pop() {return this.items.pop()
}

限制为 push 和 pop 方法添加和删除栈中元素,这样栈就自然遵循了 LIFO 原则

查看栈顶元素,想知道栈里最后添加的元素是什么,可以用 peek 方法,该方法将返回栈顶的元素

peek() {return this.items[this.items.length - 1]
}

检查栈是否为空,实现 isEmpty,如果栈为空的话将返回 true,否则就返回 false

isEmpty() {return this.items.length === 0
}

实现栈的长度

size() {return this.items.length
}

清空栈元素,实现 clear 方法,移除栈里所有的元素

clear() {this.items = []
}

以上实现了一个栈

使用 Stack 类

在深入了解栈的应用前,先来了解如何使用 Stack 类。首先需要初始化 Stack 类,然后验证一下栈是否为空(输出是 true,因为还没有往栈里添加元素)

const stack = new Stack()
console.log(stack.isEmpty()) //true

接下来,往栈里添加一些元素

stack.push(5)
stack.push(8)

调用 peek 方法(),返回栈顶的元素

console.log(stack.peek()) //8

再添加一个元素

stack.push(11)
console.log(stack.size()) //3
console.log(stack.isEmpty()) //false

继续添加元素

stack.push(15)

下图描绘了我们对栈的操作,以及栈的当前状态

调用两次 pop 方法从栈里移除两个元素

stack.pop()
stack.pop()
console.log(stack.size()) //2

在两次调用 pop 方法前,我们的栈里有四个元素。调用两次后,现在栈里仅剩下 5 和 8 了,下图描绘了这个执行过程

创建一个基于对象的 Stack 类

使用数组来存储元素,在处理大量数据时,需评估如何操作数据是最高效的,使用数组时,大部分方法的时间复杂度是 O(n) ,我们需要迭代整个数组直到找到要找的那个元素,在最坏的情况下需要迭代数组的所有位置,其中的 n 代表数组的长度。如果数组有更多元素的话,所需的时间会更长。另外,数组是元素的一个有序集合,为了保证元素排列有序,它会占用更多的内存空间。

如果我们能直接获取元素,占用较少的内存空间,并且仍然保证所有元素按照我们的需要排列,那不是更好吗?对于使用 JavaScript 语言实现栈数据结构的场景,我们也可以使用一个JavaScript 对象来存储所有的栈元素,保证它们的顺序并且遵循 LIFO 原则。我们来看看如何实现这样的行为。

首先声明一个 stack 类

class Stack {constructor() {this.count = 0 //记录栈的大小this.items = {}}
}

向栈中插入元素,因为使用的是对象, 所以 push 方法只允许我们一次插入一个元素

push(element) {this.items[this.count] = elementthis.count++
}

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

使用 Stack 类,插入元素 5,8

const stack = new Stack()
stack.push(5)
stack.push(8)

查看 stack

验证一个栈是否为空, count 属性也表示栈的大小,因此,我们可以简单地返回 count 属性的值来实现 size 方法

size() {return this.count
}

验证栈是否为空

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

从栈中弹出元素,对象中没有直接用的 api ,所以手动实现

pop() {if (this.isEmpty()) {return undefined}this.count--const result = this.items[this.count]delete this.items[this.count]return result
}

首先,我们需要检验栈是否为空。如果为空,就返回 undefined。如果栈不为空的话,我们会将 count 属性减 1,并保存栈顶的值,以便在删除它之后将它返回。

stack.pop() //8

模拟 pop 操作, 要访问到栈顶的元素(即最后添加的元素 8),我们需要访问键值为 1 的位置。因此我们将 count 变量从 2 减为 1。这样就可以访问 items[1],删除它,并将它的值返回了。

查看栈顶的元素

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

清空该栈,只需要将它的值复原为构造函数中使用的值即可

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

创建 toString 方法

在数组版本中,我们不需要关心 toString 方法的实现,因为数据结构可以直接使用数组已经提供的 toString 方法。对于使用对象的版本,我们将创建一个 toString 方法来像数组一样打印出栈的内容。

toString() {if (this.isEmpty()) {return ''}let objString = `${this.items[0]}`for (let i = 1; i < this.count; i++) {objString = `${objString}, ${this.items[i]}`}return objString
}

如果栈是空的,我们只需返回一个空字符串即可。如果它不是空的,就需要用它底部的第一个元素作为字符串的初始值,然后迭代整个栈的键,一直到栈顶,添加一个逗号以及下一个元素如果栈只包含一个元素,for循环不会执行

除了 toString 方法,我们创建的其他方法的复杂度均为 O(1),代表我们可以直接找到目标元素并对其进行操作(push、 pop 或 peek)。

保护数据结构内部元素

在创建别的开发者也可以使用的数据结构或对象时,我们希望保护内部的元素,只有我们暴露出的方法才能修改内部结构,对于 Stack 类来说,要确保元素只会被添加到栈顶,而不是栈底或其他任意位置(比如栈的中间)。

使用 WeakMap 实现类

WeakMap 可以存储键值对,其中键是对象,值可以是任意数据类型,如果用 WeakMap 来存储 items 属性(数组版本), Stack 类就是这样的:

const items = new WeakMap() //声明一个 WeakMap 类型的变量 itemsclass Stack {constructor() {items.set(this, []) //以 this(Stack 类自己的引用)为键,把代表栈的数组存入 items。}push(element) {//从 WeakMap 中取出值,即以 this 为键(行{2}设置的)从 items 中取值。const s = items.get(this)s.push(element)}pop() {const s = items.get(this)const r = s.pop()return r}
}

items 在 Stack 类里是真正的私有属性

ECMAScript 类属性提案(易读性更好)

class Stack {#count = 0#items = 0//方法
}

用栈解决问题

如何解决十进制转二进制问题,以及任意进制转换的算法。

从十进制到二进制

该十进制数除以 2(二进制是满二进一)并对商取整,直到结果是 0 为止。举个例子,把十进制的数 10 转化成二进制的数字,过程大概是如下这样。

function decimalToBinary(decNumber) {const remStack = new Stack()let number = decNumberlet rem let binaryString = ''while (number > 0) {rem = Math.floor(number % 2) //js 不区分整数和浮点数,使用 Math.floor 返回整数部分,得到余数remStack.push(rem) //放入栈里number = Math.floor(number / 2) //继续除以2,直到结果等于0时,才会停止 }while (!remStack.isEmpty()) {binaryString += remStack.pop().toString() //用 pop 方法把栈中的元素都移除,把出栈的元素连接成字符串}return binaryString
}

测试

console.log(decimalToBinary(233)) //11101001
console.log(decimalToBinary(10)) //1010
console.log(decimalToBinary(1000)) //1111101000

进制转换算法

修改上面的算法,使之能把十进制转换成基数为 2~36 的任意进制。除了把十进制数除以 2 转成二进制数,还可以传入其他任意进制的基数为参数,就像下面的算法这样。

function baseConverter(decNumber, base) {const remStack = new Stack()const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'let number = decNumberlet remlet baseString = ''if (!(base >= 2 && base <= 36)) {return ''}while (number > 0) {rem = Math.floor(number % base)remStack.push(rem)number = Math.floor(number / base)}while (!remStack.isEmpty()) {baseString += digits[remStack.pop()]}return baseString
}console.log(baseConverter(100345, 2)); // 11000011111111001
console.log(baseConverter(100345, 8)); // 303771
console.log(baseConverter(100345, 16)); // 187F9
console.log(baseConverter(100345, 35)); // 2BW0

我们只需要改变一个地方。在将十进制转成二进制时,余数是 0 或 1;在将十进制转成八进制时,余数是 0~7;但是将十进制转成十六进制时,余数是 0~9 加上 A、 B、 C、 D、 E 和 F(对应 10、 11、 12、 13、 14 和 15)。因此,我们需要对栈中的数字做个转化才可以(行{6}和行{7})。因此,从十一进制开始,字母表中的每个字母将表示相应的基数。字母 A 代表基数 11, B 代表基数 12,以此类推。

数据结构算法 二进制转十进制_数据结构 - 栈相关推荐

  1. bash 将二进制转换为十进制_用‘栈的思想编写一个十进制转换二进制、八进制或十六进制的程序...

    用'栈'的思想编写一个十进制转换二进制.八进制或十六进制的程序 根据进制转换方法,如十进制向二进制转换,将转换的十进制整数除以二进制基数(2),得到余数和商,如果商不为0,该商继续做被除数,除以基数, ...

  2. 严蔚敏数据结构c++版微盘_数据结构复习知识点总结

    <数据结构>重点在线性表.树.图.查找和排序.参考书目是<数据结构>(C语言版)严蔚敏.吴伟民编著.通过对线性表.队列.栈和数组的了解,进一步理解其含义,熟悉各种例如进栈.出栈 ...

  3. c++二进制转十进制_二进制,八进制,十进制,十六进制转换详解~

    点 击 上 方 蓝 字 关 注 我 们 哦 ^-^ 本文思维导图: 1.数制:用一组固定的数字和一套统一的规则来表示数目的方法称为数制. 进位计数制的要素: ①.数码:用来表示进制数的元素. 二进制: ...

  4. sql二进制转十进制_了解SQL十进制数据类型

    sql二进制转十进制 This article aims to walk you through the SQL Decimal data type and its usage with variou ...

  5. rsa算法c语言实现_数据结构与算法之线性表-顺序表实现(C语言版本)

    原文托管在Github: https://github.com/shellhub/blog/issues/52 数据结构与算法之线性表-顺序表实现(C语言版本) 前言 数据结构与算法是一个程序员必备的 ...

  6. 数据结构算法动图识记_【数据结构与算法】用动图解说数组、链表、跳表原理与实现...

    「初」前言 在学习数据结构与算法的过程中,感觉真的是一入算法深似海,但是越学越觉得有趣.不过我们会发现在终身学习的过程中,我们都是越学越多,不知的也越来越多,但是更渴望认知更多的知识,越是对知识感兴趣 ...

  7. 数据结构算法(二)_算法基础之前传(递归、时间复杂度、空间复杂度、二分查找)...

    什么是算法: 间而言之算法(Algorithm):一个计算过程,解决问题的方法 递归的两个特点: 调用自身 结束条件 递归示例: def func(x):if x==0:print("我的小 ...

  8. 算法最少分组法_数据结构

    20210328 https://blog.csdn.net/sinat_41144773/article/details/89530403 树.二叉树(完全二叉树.满二叉树)概念图解 二叉树:每个结 ...

  9. bash 将二进制转换为十进制_一文帮你详细图解二进制、八进制、十进制、十六进制之间的转换...

    1.背景(Contexts) 之前使用SQL把十进制的整数转换为三十六进制,SQL代码请参考:SQL Server 进制转换函数,其实它是基于二.八.十.十六进制转换的计算公式的,进制之间的转换是很基 ...

最新文章

  1. OS study plan
  2. 那个当上非洲酋长的交大才子,如今怎么样了?
  3. hdu1506 dp
  4. ASP.NET 配置log4net日志功能
  5. 如何删除 Windows.old 文件夹
  6. 红黑树、B(+)树、跳表、AVL对比
  7. $(document).ready() 和 window.onload 的区别
  8. Java 8:长期支持的堡垒
  9. 太阳直射点纬度计算公式_高中地理,常用计算公式大盘点,高中满分特辑!
  10. java弱引用在安卓中有效吗_Android 软引用和弱引用详解及实例代码
  11. hdu 1358 Period
  12. HttpClient实现通过url下载文件
  13. Flask和WebSocket
  14. StringUtil工具类之去除所有的空白字符
  15. android onenote字体,【完美解决】OneNote中英文字体不统一,微软10多年未解决的Bug!...
  16. 统计推断—假设检验(hypothesis testing)
  17. 力扣LeetCode刷题笔记总结1
  18. java开发融云web聊天
  19. 不允许一个用户使用一个以上用户名与服务器或共享资源的多重连接,中断与此服务器或共享资源的所有连接,然后再试一次
  20. 前端构建工具Gulp的学习和使用

热门文章

  1. service请求处理逻辑
  2. why there is always popup window for HANA
  3. footer bar in SAP Fiori as a service
  4. SAP Marketing Cloud功能简述(三) 营销活动内容设计和产品推荐
  5. WebIDE Initialization process
  6. 在C4C UI里嵌入CRM WebClient UI
  7. ABAP Development Tools的语法增强
  8. ABAP xml handling via SAX - 在ABAP里另一种处理xml的机制介绍
  9. SAP IBASE hierarchy remove - debug in ERP step1
  10. 点击Result list里product ID出现白屏的又一原因及分析