栈应用-经典实例

栈的实际应用非常广泛。在回溯问题中,它可以存储访问过的任务或路径、撤销的操作等, 还有递归算法也是用的栈结构.

之前两篇针对栈的实现用了两个版本, 即数组的版本和对象的版本, 这里就栈的经典应用举两个实例看看即可.

  • 进位制转化
  • 括号匹配

十进制转二进制

我们的日常生活中基本用的数都是 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

其他的就后续补充吧

posted @ 2024-04-03 13:33  致于数据科学家的小陈  阅读(17)  评论(0编辑  收藏  举报