栈是一种遵从后进先出 ( LIFO ) 原则的有序集合。新添加或待删除的元素都保存在栈的同一端,称作栈顶,另一端就叫栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底。
1.1 创建一个基于数组的栈
这里将创建一个类来表示栈。简单地从创建一个 stack-array.js 文件并声明StackA类开始。
我们需要一种数据结构来保存栈里的元素,可以选择数组。数组运行我们在任何位置添加或删除元素。由于栈遵循LIFO原则,需要对元素的插入和删除功能进行限制。接下来为栈声明一些方法。
- push(element(s)): 添加一个或几个新元素到栈顶。
- pop():移除栈顶的元素,同时返回被移除的元素。
- peek():返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶元素,仅仅返回它)。
- isEmpty():如果栈里没有任何元素就返回true,否则返回false。
- clear():移除栈里的所有元素。
- size():返回栈里的元素个数。该方法和数组的 length 属性很类似。
class StackA {
constructor() {
this.items = []
}
// 向栈添加元素:添加一个或多个元素到栈顶
push(element) {
this.items.push(element)
}
// 从栈移除元素:移除栈顶元素,同时返回被移除的元素
pop() {
return this.items.pop()
}
// 查看栈顶元素:返回栈顶元素,不对栈做任何修改
peek() {
return this.items[this.items.length -1]
}
// 检测栈是否为空:如果栈里没用任何元素就返回true,否则返回false
isEmpty() {
return this.items.length === 0
}
// 清空栈元素:移除栈里的所有元素
clear() {
this.items = []
}
}
1.2 使用StackA类
简单使用一下StackA类
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./stack-array.js"></script>
<script>
// 初始化Stack-Array类
const stacka = new StackA()
console.log(stacka.isEmpty()) // 初始化后,数据里没有元素,返回true
stacka.push(5)
stacka.push(8)
console.log(stacka.peek()) // 输出8
console.log(stacka.size()) // 输出3
console.log(stacka.isEmpty()) // 输出false
</script>
</body>
</html>
2.1 创建一个基于JavaScript对象的Stack类
创建一个Stack类最简单的方式是使用一个数组来存储其元素。在处理大量数据的时候,同样需要评估如何操作数据是最高效的。在使用数组时,大部分方法的时间复杂度的O(n)。如果数组有更多元素的话,所需的时间会更长。另外,数组是元素的一个有序集合,为了保证元素的排列有序,它会占用更多的内存空间。
可以使用一个对象来存储所有的栈元素,能直接获取元素,占用较少的内存空间,任然保证它们的顺序并且遵循LIFO原则。
先声明一个对象的Stack类, 使用count属性来记录栈的大小,也能从数据结构中添加和删除元素。
class StackO {
constructor() {
this.count = 0
this.items = {}
}
// 先栈中插入元素,只允许一次插入一个元素
push(element) {
this.items[this.count] = element
this.count++
}
// 返回它的大小
size() {
return this.count
}
// 验证一个栈是否为空
isEmpty() {
return this.count === 0
}
// pop方法同样返回了从栈中移除的元素。由于没有使用数组来存储数据,需要手动实现移除元素的逻辑。
pop() {
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.items = {}
this.count = 0
}
// 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
}
}
2.2 使用StackO类
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./stack-object.js"></script>
<script>
// 初始化Stack-Object类
const stacko = new StackO()
console.log(stacko.isEmpty())
stacko.push(5)
stacko.push(7)
stacko.push(8)
console.log(stacko.peek()) // 输出8
console.log(stacko.toString()) // 5, 7, 8
</script>
</body>
</html>
3.1 用栈解决问题
3.1.1 从十进制到二进制
要把十进制转化成二进制,我们可以将该十进制数除以二(二进制是满二进一)并对商取整,直到结果是0为止。比如:
10 / 2 === 5 rem === 0
5 / 2 === 2 rem === 1
2 / 2 === 1 rem === 0
1 / 2 === 0 rem === 1
// 先得到的余数就压入栈中。 输出就等于将余数移出栈。 所以结果就为:1010
下面用js来实现一下:
// 十进制转二进制
// remStack 是前面创建的Stack类,传给函数内部使用
function decimalToBinary(decNumber, remStack) {
let number = decNumber
let rem
let binaryString = ''
while (number > 0) {
rem = Math.floor(number % 2)
remStack.push(rem)
number = Math.floor(number / 2)
}
while (!remStack.isEmpty()) {
binaryString += remStack.pop().toString()
}
return binaryString
}
在这段代码里,当除法的结果不为0时,会得到一个余数,并放到栈里。然后让结果继续除以2。
const remStack1 = new StackO()
console.log(decimalToBinary(233, remStack1)) // 11101001
console.log(decimalToBinart(10, remStack1)) // 1010
可以修改之前的算法,使之能把十进制转换成基数为2-36的任意进制。除了把十进制转成二进制,还可以传入其他任意进制的基数为参数。
// 十进制转其他进制
function baseConverter(decNumber, base, remStack) {
const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
let number = decNumber;
let rem;
let 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
}
只需要改变一个地方。在将十进制转成二进制时,余数是0或1;在将十进制转成八进制时,余数是0~7;但是将十进制转成十六进制时,余数是0-9加上A、B、C、D、E、F。因此,需要对栈中的数字做个转化才行。
因此,从十一进制开始,字母表中的每个字母将表示相应的基数。字母A代表基数11,B代表基数12,以此类推。
const remStack2 = new StackO()
console.log(baseConverter(100345, 2, remStack2)) // 转为二进制:11000011111111001
console.log(baseConverter(100345, 8, remStack2)) // 转为8进制:303771
console.log(baseConverter(100345, 16, remStack2)) // 转为16进制:187F9
console.log(baseConverter(100345, 35, remStack2)) // 转为35进制:2BW0