栈应用-经典实例
栈的实际应用非常广泛。在回溯问题中,它可以存储访问过的任务或路径、撤销的操作等, 还有递归算法也是用的栈结构.
之前两篇针对栈的实现用了两个版本, 即数组的版本和对象的版本, 这里就栈的经典应用举两个实例看看即可.
- 进位制转化
- 括号匹配
十进制转二进制
我们的日常生活中基本用的数都是 10 进制, 但对于计算机来说所有的处理都是 2进制. 这里就涉及一个转化的算法.
我们通常称为 "除 N 取余法".
举个例子来说明, 就以将10进制下的 "10" 转为 2进制应该是多少.
10 / 2 = 5 ... 0
5 / 2 = 2 ... 1
2 / 2 = 1 ... 0
1 / 2 = 0 ... 1
直至商为 0 为止,
然后再将余数, 倒序输出
即 1010
可以看到这个过程就能很好利用栈结构.
每次取到的余数都进行入栈, 然后最后再进行出栈, 不就解决了吗.
// 数组版本的栈结构
class Stack {
constructor() {
this.arr = []
}
// 入栈
push(item) {
this.arr.push(item)
}
// 出栈
pop() {
return this.arr.pop()
}
// 查看栈顶元素
peek() {
return this.arr[this.arr.length - 1]
}
// 是否为空
isEmpty() {
return this.arr.length == 0
}
// 栈的元素个数或长度
size() {
return this.arr.length
}
// 清空栈
clear() {
this.arr = []
}
}
// 十进制转二进制
function decimalToBinary(decimalNum) {
const stack = new Stack()
let num = decimalNum
let rem
let binaryString = ''
while (num > 0) {
rem = Math.floor(num % 2)
num = Math.floor(num / 2)
stack.push(rem)
}
while (!stack.isEmpty()) {
binaryString += stack.pop().toString()
}
return binaryString
}
console.log('10 的二进制是: ', decimalToBinary(10));
console.log('100 的二进制是: ', decimalToBinary(100));
console.log('666 的二进制是: ', decimalToBinary(666));
console.log('233 的二进制是: ', decimalToBinary(233));
10 的二进制是: 1010
100 的二进制是: 1100100
666 的二进制是: 1010011010
233 的二进制是: 11101001
十进制转 N 进制
如果要转成 8进制, 7进制, 20进制.... 对上面拓展一下就好了.
在将十进制转成二进制时,余数是0或1;
在将十进制转成八进制时,余数是0~7;
为了方便表示基数呢, 假设我们最多是 2-36进制. 即 9 个数字假设23个大写英文字母.
比如 A 就是表示 10, B 表示11 ... Z 表示 35
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
注意一定要从 0
开始哦, 这样一来,
十进制转成十六进制时,余数是0~9加上A、B、C、D、E和F(对应10、11、12、13、14和15)
// 对象版本的栈结构
class Stack {
constructor() {
// 用一个 count 属性来记录栈的大小
this.count = 0
this.obj = {}
}
// 入栈
push(item) {
this.obj[this.count] = item
this.count += 1
}
// 栈是否为空
isEmpty() {
return this.count == 0
}
// 栈的大小
size() {
return this.count
}
// 出栈
pop() {
if (this.isEmpty()) return undefined
this.count= this.count - 1
const item = this.obj[this.count]
delete this.obj[this.count]
return item
}
// 查看栈顶元素
peek() {
if (this.isEmpty()) return undefined
return this.obj[this.count -1]
}
// 清空栈
clear() {
this.count = 0
this.obj = {}
}
// 当然也可以不断地 pop
clear2() {
while (! this.isEmpty()) {
this.pop()
}
}
// toString 方法
toString() {
if (this.isEmpty()) return ''
let objString = `${this.obj[0]}`
for (let i = 1; i < this.count; i++) {
objString = `${objString}, ${this.obj[i]}`
}
return objString
}
}
// 十进制转 N 进制, N ~ [2, 36]
function decimalConverter(decimalNum, base) {
const stack = new Stack()
let num = decimalNum
let rem
let baseString = ''
const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
// const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
if (base < 2 || base > 36) return ''
while (num > 0) {
rem = Math.floor(num % base)
num = Math.floor(num / base)
stack.push(rem)
}
while (!stack.isEmpty()) {
baseString += digits[stack.pop()]
}
return baseString
}
// test
console.log('123456 的2进制是: ', decimalConverter(123456, 2));
console.log('123456 的8进制是: ', decimalConverter(123456, 8));
console.log('123456 的16进制是: ', decimalConverter(123456, 16));
console.log('123456 的32进制是: ', decimalConverter(123456, 32));
console.log('123456 的40进制是: ', decimalConverter(123456, 40));
PS F:\algorithms> node stack_case2.js
123456 的2进制是: 11110001001000000
123456 的8进制是: 361100
123456 的16进制是: 1E240
123456 的32进制是: 3OI0
123456 的40进制是:
括号匹配
很多编程语言都会用到 (), {}
进行代码的分割, 嵌套等, 因此在编辑器层面通常需要对括号是否对称的进行验证.
以一个算法题为例. 给定一个只包含括号 (, ), {, }
的字符串 s, 判断其是否有效(对称)
- 左括号必须使用相同的右括号闭合
- 左括号必须以正确的顺序闭合
- 每个右括号都有对应一个相同类型的左括号
其实就可以通过栈结构来解决, 即形如这样的 (((( )))
, 进行循环遍历, 遇到左括号就就入栈, 等后面遇到右括号就将栈顶的左括号移除. 这样最后如果是对称的, 那栈应该是空的, 否则就是不匹配的.
function isValid(str) {
const stack = new Stack()
const map = {
'(': ')',
'{': '}'
}
for (s of str) {
if (s in map) {
stack.push(s)
} else {
// 遇到右括号则对全是左括号的栈进行出栈一次,
// 看其对应的右侧和当前是否一致的
topItem = stack.pop()
if (s != map[topItem] ) return false
}
}
// 如果最后是匹配的, 则最后栈是空的
return stack.isEmpty()
}
console.log(isValid('((()))'));
console.log(isValid('(()))'));
console.log(isValid('{{]{'));
console.log(isValid('{{{{{{{}}}}}}}'));
然后顺带补充一些算法题吧, 用栈的.
删除字符串中所有相邻重复项
- 输入: "abbaca"
- 输出: "ca"
先是 bb 先删除了, 变成 "aaca", 然后再删除 aa , 最后剩下 "ca"
思路就是, 先建一个空栈 stack,
然后开始遍历每个字符, 每次从栈顶 pop 出元素 item 和 当前的字符 char 相比较
如果
item 不等于 char 则又将 item 压栈, 然后再将当前元素压栈
否则, 则不处理
最后拼接出栈元素即可
function removeDuplicates (str) {
const stack = new Stack()
for (let char of str) {
item = stack.pop()
if (item != char) {
// 和栈顶弹出元素不等时, 先将其压回去, 再压当前字符
stack.push(item)
stack.push(char)
}
// 相等说明重复, 不用处理因为 stack 也 pop 了的
}
// 数组toString: return this.arr.join("")
return stack.toString()
}
console.log(removeDuplicates('abbaca'));
console.log(removeDuplicates('abbbbbdddnba'));
console.log(removeDuplicates('abbaacdadfsdaaccca'));
PS F:\algorithms> node stack_case3x.js
ca
abdnba
acdadfsdca
其他的就后续补充吧