队列

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-04-22

队列是一种列表,不同的是队列只能在队尾插入元素,在队首删除元素。队列用于存储按顺序排列的数据,一种先进先出(First-In-First-Out, FIFO)的数据结构,可以将队列想象成在银行前排队的人群,排在最前面的人第一个办理业务,新来的人只能在后面排队,直到轮到他们为止。

队列的两种主要操作是:向队列中插入新元素和删除队列中的元素,插入的操作也叫入队,删除操作也叫出队,入队操作在队尾插入新元素,出队操作删除队头的元素。另外一项重要操作是读取队头的元素 peek,使用 clear 方法可以清空队列。

队列的实现

在JavaScript使用数组能够很简单的实现队列。

function Queue() {
  this.dataStore = [];
  this.enqueue = enqueue;
  this.dequeue = dequeue;
  this.front = front;
  this.back = back;
  this.toString = toString;
  this.empty = empty;
  this.count = count;
}

// 入队
function enqueue(element) {
  this.dataStore.push(element)
}

// 出队
function dequeue() {
  return this.dataStore.shift()
}

function front() {
  return this.dataStore[0]
}

function back() {
  return this.dataStore[this.dataStore.length - 1]
}

function toString() {
  var retStr = "";
  for (var i = 0; i < this.dataStore.length; ++i) {
    retStr += this.dataStore[i] + '\n';
  }
  return retStr;
}

function empty() {
  return this.dataStore.length === 0;
}

function count() {
  return this.dataStore.length;
}


// example 1
var q = new Queue();
q.enqueue('Meredith')
q.enqueue('Cynthia')
q.enqueue('Jennifer')
console.log(q.toString());
// Meredith
// Cynthia
// Jennifer

q.dequeue()
console.log(q.toString());
// Cynthia
// Jennifer

console.log('Front of queue: ' + q.front());
console.log('Back of queue: ' + q.back());
// Front of queue: Cynthia
// Back of queue: Jennifer

使用队列:方块舞的舞伴分配问题

场景:当男男女女来到舞池,他们按照自己的性别排成两队,当舞池中有地方空出来时,选两个队列中的第一个人组成舞伴。他们身后的人各自向前移动一位,变成新的队首。当一队舞伴迈入舞池时,主持人会大声喊出他们的名字。当一队舞伴走出舞池,且两排队伍中有任意一队没人时,主持人也会把这个情况告诉大家。

将跳舞的人员的姓名存储在一个文本文件中 dancers.txt:

F Allison
M Frank
M Mason
M Clayton
F Cheryl
M Raymond
F Jennifer
M Bryan
M David
M Danny
F Aurora

代码实现:

const fs = require('fs')

function read(path) {
  var res = fs.readFileSync(path, 'utf-8')
  return res;
}

function Dancer(name, sex) {
  this.name = name;
  this.sex = sex;
}

function getDancers(males, females) {
  var names = read('dancers.txt').split('\n')
  for (var i = 0; i < names.length; ++i) {
    var [sex, name] = names[i].trim().split(' ');
    if (sex === 'F') {
      females.enqueue(new Dancer(name, sex))
    } else {
      males.enqueue(new Dancer(name, sex))
    }
  }
}

function dance(males, females) {
  var person
  console.log('The dance partners are: \n');
  while (!males.empty() && !females.empty()) {
    // 输出当前跳舞的男士和女士是谁
    person = females.dequeue()
    console.log('Female dancer is: ' + person.name);
    person = males.dequeue()
    console.log('and the male dancer is: ' + person.name + '\n');
  }
}

var maleDancers = new Queue();
var femaleDancers = new Queue();
getDancers(maleDancers, femaleDancers)
dance(maleDancers, femaleDancers)

// 组队完毕之后,如果还有人在排队,打印还有多少位正在等待
if (maleDancers.count() > 0) {
  console.log(`There are ${maleDancers.count()} male dancers waiting do dance .`);
}

if (femaleDancers.count() > 0) {
  console.log(`There are ${femaleDancers.count()} female dancers waiting do dance .`);
}

// 执行结果
// The dance partners are: 

// Female dancer is: Allison
// and the male dancer is: Frank

// Female dancer is: Cheryl
// and the male dancer is: Mason

// Female dancer is: Jennifer
// and the male dancer is: Clayton

// Female dancer is: Aurora
// and the male dancer is: Raymond

// There are 3 male dancers waiting do dance .

使用队列:对数据进行排序

对于0~99的数字,基数排列是将数据集扫描两次,第一次按个位上的数字进行排序,第二次按十位上的数字进行排序。每个数字根据对应位上的数值被分在不同的盒子。

假设有以下数字:

91, 46, 85, 15, 92, 35, 31, 22

第一次按照个位上的数字进行排序,结果如下:

Bin 0:
Bin 1: 91, 31
Bin 2: 92, 22
Bin 3: 
Bin 4:
Bin 5: 85, 15, 35
Bin 6: 46
Bin 7:
Bin 8:
Bin 9:

按顺序取出:

91, 31, 92, 22, 85, 15, 35, 46

然后根据十位上的数字进行排序:

Bin 0:
Bin 1: 15
Bin 2: 22
Bin 3: 31, 35
Bin 4: 46
Bin 5: 
Bin 6: 
Bin 7:
Bin 8: 85,
Bin 9: 91, 92

按顺序取出即为排序好的数字:

15, 22, 31, 35, 46, 85, 91, 92

代码实现如下:

// 创造十个盒子
var queues = [];
for (var i = 0; i < 20; i++) {
  queues.push(new Queue())
}

// 随机创造20个0~99中的数字组成数组
var nums = []
for (var i = 0; i < 20; i++) {
  nums.push(Math.floor(Math.random() * 100))
}


/**
 * 进行基数排序
 *
 * @param {number[]} nums 需要排序的数组
 * @param {Queue[]} queues 用来存储排序结果的“盒子” 由10个Queue组成
 * @param {1|10} digit 用来分别当前是根据个位排序还是根据十位排序 
 */
function distribute(nums, queues, digit) {
  for (var i = 0; i < nums.length; i++) {
    if (digit === 1) {
      // 根据个位排序
      // nums[i] % 10 即取出当前数字的个位
      // 个位是几 就将该数入队到第几个盒子(Queue)
      var index = nums[i] % 10;
      queues[index].enqueue(nums[i])
    } else {
      // 根据十位排序
      // Math.floor(nums[i] / 10) 取出数字的十位
      // 十位是几 就将该数入队到第几个盒子(Queue)
      var index = Math.floor(nums[i] / 10)
      queues[index].enqueue(nums[i])
    }
  }
}


/**
 * 用来将 基数排列过的数字 按顺序从十个盒子(Queue)中取出并排列在nums中
 *
 * @param {Queue[]} queues 
 * @param {number[]} nums
 */
function collect(queues, nums) {
  var i = 0;
  for (var j = 0; j < queues.length; j++) { // 外循环用来处理一个个的盒子
    while(!queues[j].empty()) { // 内循环用来确保取空当前盒子(Queue)
      nums[i++] = queues[j].dequeue();
    }
  }
}

// 展示数组
function displayArray(arr) {
  console.log(arr.join(', '));
}

console.log('Before radix sort:');
displayArray(nums)

// 进行个位排序
distribute(nums, queues, 1)
// 按顺序取出并形成新的数组
collect(queues, nums)

// 进行十位排序
distribute(nums, queues, 10)
// 按顺序取出并形成新的数组
collect(queues, nums)

console.log();
console.log('After radix sort:');
displayArray(nums)

// Before radix sort:
// 49, 58, 41, 12, 97, 10, 46, 49, 49, 62, 4, 70, 87, 21, 57, 87, 40, 51, 53, 88

// After radix sort:
// 4, 10, 12, 21, 40, 41, 46, 49, 49, 49, 51, 53, 57, 58, 62, 70, 87, 87, 88, 97

优先队列

在一般情况下,从队列中删除的元素,一定是率先入队的元素。但是也有一些使用队列的应用,在删除元素时不必遵守先进先出的约定,这种情况需要使用到优先队列。

从优先队列时删除元素时,需要考虑优先权的限制,比如医院急诊科的候诊室,就是一个采取优先队列的例子,当病人进入后候诊室时,分诊护士会评估患者病情的严重程度,然后给一个优先级代码,高优先级的患者优先就医,同来的优先级按照先来先服务的顺序就医。

// 病人类,拥有姓名和优先级,code越小 优先级越高
function Patient(name, code) {
  this.name = name;
  this.code = code;
}

// 使用组合继承 让优先队列继承于普通队列
function PriorityQueue() {
}

// 继承
PriorityQueue.prototype = new Queue()
PriorityQueue.prototype.constructor = PriorityQueue;

// 定义自己的dequeue逻辑,按照优先级进行出队操作
PriorityQueue.prototype.dequeue = function () {
  var entry = 0;
  // 找出最小的code
  for (var i = 1; i < this.dataStore.length; ++i) {
    if (this.dataStore[i].code < this.dataStore[entry].code) {
      entry = i;
    }
  }
  // 对其进行出队操作
  return this.dataStore.splice(entry, 1)
}

// 定义自己的toString
PriorityQueue.prototype.toString = function () {
  var retStr = ''
  for (var i = 0; i < this.dataStore.length; i++) {
    retStr += `${this.dataStore[i].name} code: ${this.dataStore[i].code} \n`
  }
  return retStr;
}

var pq = new PriorityQueue()

// 在队列中添加不同优先级的病人
var p = new Patient('Smith', 5)
pq.enqueue(p)

p = new Patient('Jones', 4)
pq.enqueue(p)

p = new Patient('Fehrenbach', 6)
pq.enqueue(p)

p = new Patient('Brown', 1)
pq.enqueue(p)

p = new Patient('Ingram', 1)
pq.enqueue(p)

console.log('Patient List:');
console.log(pq.toString());

var seen = pq.dequeue();
console.log('Patient being treated: ' + seen[0].name);
console.log('Patient waiting to be seen: ');
console.log(pq.toString());

var seen = pq.dequeue();
console.log('Patient being treated: ' + seen[0].name);
console.log('Patient waiting to be seen: ');
console.log(pq.toString());

var seen = pq.dequeue();
console.log('Patient being treated: ' + seen[0].name);
console.log('Patient waiting to be seen: ');
console.log(pq.toString());

// Patient List:
// Smith code: 5 
// Jones code: 4 
// Fehrenbach code: 6 
// Brown code: 1 
// Ingram code: 1 

// Patient being treated: Brown
// Patient waiting to be seen: 
// Smith code: 5 
// Jones code: 4 
// Fehrenbach code: 6 
// Ingram code: 1 

// Patient being treated: Ingram
// Patient waiting to be seen: 
// Smith code: 5 
// Jones code: 4 
// Fehrenbach code: 6 

// Patient being treated: Jones
// Patient waiting to be seen: 
// Smith code: 5 
// Fehrenbach code: 6