JavaScript数据结构与算法之 "队列和双端队列"

282 阅读3分钟

队列数据结构

  • 队列数据结构遵循先进先出原则的一组有序项
  • 队列在头部移除数据,在尾部添加数据。最新添加的数据必须排在队列的最末端
  • 在现实生活中最常见的队列就是排队

队列数据结构的实现

  • 通过对象我们可以实现队列
  • 队列包含的方法
    • enqueue(): 向队列尾部添加一个新的元素
    • dequeue(): 移除队列的第一个元素,并返回移除的元素
    • peek(): 返回队列中的第一个元素---最先被添加,也是最先被返回的元素,不对队列做任何改动
    • isEmpty(): 判断队列中是否有元素,没有返回true,有方法false
    • clear(): 清空队列
    • size(): 返回队列中元素的个数
    • toString()
  • 代码
    /*创建队列的类*/
    class Queue {
        constructor() {
            this.count = 0; //队列中元素的计数
            this.lowestCount = 0; // 队列中的元素的最小计数
            this.items = {};  // 存储队列中元素的对象
        }
    
        // enqueue(): 向队列尾部添加一个新的元素
        enqueue(element) {
            this.items[this.count] = element;
            this.count += 1;
        }
    
        // dequeue(): 移除队列的第一个元素,并返回移除的元素
        dequeue() {
            if (this.isEmpty()) {
                return undefined;
            }
    
            const result = this.items[this.lowestCount];
            delete this.items[this.lowestCount];
            this.lowestCount += 1;
    
            return result;
        }
    
        // peek(): 返回队列中的第一个元素---最先被添加,也是最先被返回的元素,不对队列做任何改动
        peek() {
            if (this.isEmpty()) {
                return undefined;
            }
    
            return this.items[this.lowestCount];
        }
    
        // isEmpty(): 判断队列中是否有元素,没有返回true,有方法false
        isEmpty() {
            return this.count - this.lowestCount === 0;
        }
    
        // clear(): 清空队列
        clear() {
            this.count = 0;
            this.lowestCount = 0;
            this.items = {};
        }
    
        // size(): 返回队列中元素的个数
        size() {
            return this.count - this.lowestCount;
        }
    
        // toString()
        toString() {
            if (this.isEmpty()) {
                return '';
            }
    
            let str = `${this.items[this.lowestCount]}`;
            for (let i = this.lowestCount + 1; i < this.count; i += 1) {
                str = `${str},${this.items[i]}`;
            }
    
            return str;
        }
    }
    

双端队列数据结构

  • 双端队列是一种允许我们同时从前端和后端添加和移除数据的特殊队列
  • 在计算机科学中,双端队列一个常见应用是存储一系列的撤销操作
  • 由于双端队列同时遵守了先进先出和后进后出的原则,可以所它是把队列和栈相结合的一种数据结构

双端队列数据结构的实现

  • 双端队列的方法:
    • addFront(element):该方法在双端队列的前端添加一个新的数据
    • addBack(element):该方法在双端队列的后端添加一个新的数据
    • removeFront():该方法从双端队列的前端移除一个数据
    • removeBack():该方法从双端队列的后端移除一个数据
    • peekFront():该方法返回双端队列的第一个元素,不对队列做任何修改
    • peekBack():该方法返回双端队列的最后一个元素,不对队列做任何修改
    • isEmpty(): 判断队列中是否有元素,没有返回true,有方法false
    • clear(): 清空队列
    • size(): 返回队列中元素的个数
    • toString()
  • 代码
    /*创建双端队列的类*/
    class Deque {
        constructor() {
            this.count = 0; //双端队列的技术器
            this.lowestCount = 0; //双端队列的最小计数器
            this.items = {};  // 存储双端队列数据的对象
        }
    
        //  addFront(element):该方法在双端队列的前端添加一个新的数据
        addFront(element) {
            if (this.isEmpty()) {
                this.addBack(element);
            } else if (this.lowestCount > 0) {
                this.lowestCount -= 1;
                this.items[this.lowestCount] = element;
            } else {
                for (let i = this.count; i > 0; i -= 1) {
                    this.items[i] = this.items[i - 1];
                }
    
                this.lowestCount = 0;
                this.count += 1;
                this.items[0] = element;
            }
        }
    
        //  addBack(element):该方法在双端队列的后端添加一个新的数据
        addBack(element) {
            this.items[this.count] = element;
            this.count += 1;
        }
    
        //  removeFront():该方法从双端队列的前端移除一个数据
        removeFront() {
            if (this.isEmpty()) {
                return undefined;
            }
    
            const result = this.items[this.lowestCount];
            delete this.items[this.lowestCount];
            this.lowestCount += 1;
            return result;
        }
    
        //  removeBack():该方法从双端队列的后端移除一个数据
        removeBack() {
            if (this.isEmpty()) {
                return undefined;
            }
    
            this.count -= 1;
            const result = this.items[this.count];
            delete this.items[this.count];
    
            return result;
        }
    
        //  peekFront():该方法返回双端队列的第一个元素,不对队列做任何修改
        peekFront() {
            if (this.isEmpty()) {
                return undefined;
            }
            return this.items[this.lowestCount];
        }
    
        //  peekBack():该方法返回双端队列的最后一个元素,不对队列做任何修改
        peekBack() {
            if (this.isEmpty()) {
                return undefined;
            }
            return this.items[this.count - 1];
        }
    
        //  isEmpty(): 判断队列中是否有元素,没有返回true,有方法false
        isEmpty() {
            return this.count - this.lowestCount === 0;
        }
    
        //  clear(): 清空队列
        clear() {
            this.count = 0;
            this.lowestCount = 0;
            this.items = {};
        }
    
        //  size(): 返回队列中元素的个数
        size() {
            return this.count - this.lowestCount;
        }
    
        //  toString()
        toString() {
            if (this.isEmpty()) {
                return '';
            }
    
            let str = `${this.items[this.lowestCount]}`;
            for (let i = this.lowestCount + 1; i < this.count; i += 1) {
                str = `${str},${this.items[i]}`;
            }
    
            return str;
        }
    
    }
    

队列和双端队列的使用

击鼓传花游戏

  • 我们使用队列来解决一个名为击鼓传花的游戏。在这个游戏中孩子们围成一圈
  • 代码
    /*击鼓传花游戏*/
    const hotPotato = (elementList, num) => {
        const queue = new Queue();
        let eliminatedList = [];  // 淘汰人员的名单
    
        // 将elementList 存入队列中
        elementList.forEach((element) => queue.enqueue(element));
    
        // 如果队列的size大于1,就一致进行游戏
        while (queue.size() > 1) {
            for (let i = 0; i < num; i += 1) {
                queue.enqueue(queue.dequeue());
            }
    
            eliminatedList.push(queue.dequeue());
        }
    
        return {
            eliminated: eliminatedList,
            winner: queue.dequeue(),  //胜利者
        };
    };
    
    /*使用下面的代码来尝试hotPotato算法*/
    const names = ['John', 'Tom', 'Jack', 'Curry', 'Bil', 'Camila', 'Jam'];
    const result = hotPotato(names, 5);
    
    result.eliminated.forEach((name) => {
        console.log(`${name}在游戏中被淘汰`);
    })
    
    console.log(`胜利者:${result.winner}`);
    
    /*结果*/
    // Camila在游戏中被淘汰
    // Bil在游戏中被淘汰
    // Jam在游戏中被淘汰
    // Tom在游戏中被淘汰
    // John在游戏中被淘汰
    // Curry在游戏中被淘汰
    // 胜利者:Jack
    
    

回文检查器

  • 回文是正反都能读通的单词、词组、数或一系列的序文,例如:madam或 “你好你”
  • 可以使用双端队列来编写一个检测回文的算法
  • 代码:
    /*回文检测*/
    const palindromeCheck= (str) => {
        if (str === null || str === undefined || str.length === 0) {
            return false;
        }
    
        const deque = new Deque();  // 存储回文字符的双端队列
        const lowerStr = str.toLowerCase().split(' ').join('');  //去除空格,并转换为小写
        let isEqual = true;
        let firstChar, lastChar;
    
        while (deque.size() > 1 && isEqual) {
            firstChar = deque.removeFront();
            lastChar = deque.removeBack();
    
            if (firstChar !== lastChar) {
                isEqual = false;
            }
        }
    
        return isEqual;
    }
    
    /*测试*/
    console.log('a', palindromeCheck('a'));
    console.log('madam', palindromeCheck('madam'));
    console.log('你好你', palindromeCheck('你好你'));
    console.log('Was it a car or a cat I saw', palindromeCheck('Was it a car or a cat I saw'));
    
    /*结果*/
    // a true
    // madam true
    // 你好你 true
    // Was it a car or a cat I saw true