Stack and Queue in JavaScript(Javascript中的数据结构之栈和队列)

696 阅读4分钟

前言: 最近因为学习数据结构与算法,所以就写把自己学到的东西分享出来,以及自己的一些感悟。

这篇文章主要讲解Javascript中一些常见的数据结构及其实现。

Stack (栈)

栈是一种后进先出(Last In First Out/LIFO)的数据结构。

栈的实现:

实现原理: 主要是通过对 array 不同的操作方式,来达到我们想要的结果

class Stack {
   constructor() {
        this.items = []
   }
   
   is_empty() {
        // 判断栈是否为空
        return this.items.length === 0
   }
   
   push(item) {
        // 添加到栈的顶部 
        this.items.append(item)
   }
   
   pop() {
        // 从栈的顶部删除
        return this.items.pop()
   }
   
   peek() {
        // 查找栈顶的元素
        return this.items[this.items.length - 1]
   }
   
   size() {
        // 返回栈的大小
        return this.items.length
   }
}

栈的应用:

题目: 简单的符号匹配。如((())) ==> true,([()]) ==> true,(([)) ==> false

/**
* 利用栈的后进先出特性,先将左半边的符号入栈,当匹配到后半边的时候,
* 依次出栈,依次匹配,如果能匹配上,并且最后栈为空,说明是完全匹配。
*/
function parChecker(str) {
    let s = new Stack()
    let index = 0
    while (index < str.length) {
        let sym = str[index]
        if (sym === '([{') {
            s.push(sym)
        } else {
            if (s.is_empty()) {
                return false
            } else {
                let top = s.pop()
                if (!matches(top, sym)) {
                    return false
                }
            }
        }
        index += 1
    }
    if (s.is_empty()) {
        return true
    } else {
        return false
    }
}

function matches(open, close) {
    let opens = '([{'
    let closes = ')}'
    return opens.index(open) === closes.index(close)
}

console.log(parChecker('({[()]})')) // true
console.log(parChecker('({[)})'))

Queue (队列)

队列是一种先进先出(First In First Out/FIFO)的数据结构。

队列的实现

实现原理:和栈一样都是通过array来构建的,不同在于:栈是从末尾删除,队列是从头部删除

class Queue {
    constructor() {
        this.items = []
    }
    
    is_empty() {
        return this.items.length === 0
    }
    
    size() {
        return this.items.length
    }
    // 入队
    enqueue(data) {
        this.items.push(data)
    }
    // 出队,删除的是头部元素
    dequeue() {
        return this.items.shift()
    }
}

队列的应用:

著名的约瑟夫环问题。

/*
比如有5个人玩游戏([1, 2, 3, 4, 5]),规定7个一轮回,直至剩下最后一个人。
(拿炸金花来说,发牌一般是发给下一个人)
第一次: 2, 3, 4, 5, 1, 2, 3。 最后一张牌是发给3,所以3淘汰。[1, 2, 4, 5]
第二次: 因为3淘汰了,所以4成为发牌人。 5, 1, 2, 4, 5, 1, 2。最后是2,所以2淘汰。[1, 4, 5]
第三次: 因为2淘汰了,所以4成为发牌人。5, 1, 4, 5, 1, 4,5。最后是5,所以5淘汰 [1, 4]
第四次: 因为5淘汰了,所以1成为发牌人。4,1,4,1, 4, 1, 4,所以4淘汰,最后只剩下1.

对于这个问题,我们可以用队列来解决。(后面我们还会说到通过单向循环链表来解决,不过略比队列要复杂点)
思路是: 通过不断地入队和出队,将每次循环需要删除的元素‘移动’到队列的最前端(也就是入队最早的),来达到删除他的目的
*/

function yueSeFu(list, num) {
    let q = new Queue()
    for (let i = 0; i < list.length; i++) {
        q.enqueue(list[i])
    }
    
    while (q.size() > 1) {
        for (let j = 0; j < num; j++) {
            q.enqueue(q.dequeue())
        }
        q.dequeue()
    }
    return q.dequeue()
}

console.log(yueSeFu([1, 2, 3, 4, 5], 7)) // 1

双端队列

在队列的基础上,既能左边先进先出,也能又边先进先出。(也称为双端队列)

双端队列的实现

实现原理: 通过对list的不同操作来达到我们想要的效果

class Deque {
    constructor() {
        this.items = []
    }
    
    is_empty() {
        return this.items.length === 0
    }
    
    size() {
        return this.items.length
    }
    
    // 在后面添加
    addRear(data) {
        this.items.push(data)
    }
    
    // 在前面添加
    addFront(data) {
        this.items.unshift(data)
    }
    
    // 在后面删除
    removeRear() {
        return this.items.pop()
    }
    
    // 在前面删除
    removeFront() {
        return this.items.shift()
    }
}

双端队列的应用:

回文字符串检查。 如'abcba' ==> true / 'abcd' ==> false

/**
通过双端队列就能很好的检查一个字符串是否是回文字符串。
思路:前面删除的字符串和后面删除的字符串是否一样
*/

function palChecker(str) {
    let deque = new Deque()
    for (let i = 0; i < str.length; i++) {
        deque.addRear(str[i])
    }
    
    while (deque.size() > 1) {
        let rear = deque.removeRear()
        let front = deque.removeFront()
        
        if (rear !== front) {
            return false
        }
    }
    return true
}

console.log('abcba') // true
console.log('abcd') // false

结尾:写了一遍,自己又加深了一边记忆。每种数据结构都有他们对应的应用场景,只有很好的理解他们,才能以最简单的方式正确的解决最复杂的问题。

如果文章有不对的地方,欢迎指正。By the way, 最近在学Python。如果想在python中实现这些数据结构,代码基本上是一样的。只是在Python中操作List的方法略有不同而已。

下次会更新链表(单向链表,单向循环链表,双向链表,双向循环链表)。谢谢!