第4章“队列”
队列是遵循FIFO(First In First Out,先进先出,也称为先来先服务)原则的一组有序的项。
队列在尾部添加新元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾
常见例子就是:排队
一.创建队列
- 我们需要创建自己的类来表示一个队列。先从最基本的声明类开始:
function Queue() {
//这里是属性和方法
}
- 首先需要一个用于存储队列中元素的数据结构。我们可以使用数组,就像在上一章 Stack 类
中那样使用(你会发现 Queue 类和 Stack 类非常类似,只是添加和移除元素的原则不同):
var items = [];
- 需要声明一些队列可用的方法:
enqueue(element(s)) :向队列尾部添加一个(或多个)新的项。
dequeue() :移除队列的第一(即排在队列最前面的)项,并返回被移除的元素。
front() :返回队列中第一个元素——最先被添加,也将是最先被移除的元素。队列不
做任何变动(不移除元素,只返回元素信息——与 Stack 类的 peek 方法非常类似)。
isEmpty() :如果队列中不包含任何元素,返回 true ,否则返回 false 。
size() :返回队列包含的元素个数,与数组的 length 属性类似。
- 向队列添加新元素enqueue 。新的项只能添加到队列末尾
由于队列遵循先进先出原则,最先添加的项也是最先被移除的。
function Queue() {
//这里是属性和方法
var items = [];
// 向队列添加新元素。新的项只能添加到队列末尾
this.enqueue = function(element){
items.push(element);
console.log(items);Array(1)
};
}
var queue = new Queue();
queue.enqueue(3);//[3]
queue.enqueue(5);//[3,5]
- 从队列移除项dequeue.由于队列遵循先进先出原则,最先添加的项也是最先被移除的。
function Queue() {
//这里是属性和方法
var items = [];
// 向队列添加新元素。新的项只能添加到队列末尾
this.enqueue = function(element){
items.push(element);
console.log(items);Array(1)
};
this.dequeue = function(){
return items.shift();
};
}
var queue = new Queue();
queue.enqueue(3);//[3]
queue.enqueue(5);//[3,5]
queue.dequeue();//移除第一个元素
要想看到dequeue的移除效果可以把所有元素打印出来,
也可以看现在队列(数组)的长度
- 只有 enqueue 方法和 dequeue 方法可以添加和移除元素, 这样就确保了 Queue 类遵循先进先出原则
- 额外的辅助方法:front/isEmpty/size/print
返回队列最前面的项:front
this.front = function(){
return items[0];
};
isEmpty 方法。如果队列为空,它会返回 true ,否则返回 false
(注意这个方法和Stack 类里的一样)
this.isEmpty = function(){
return items.length == 0;
};
size 方法也跟 Stack 类里的一样
判断数组里的元素个数
this.size = function(){
return items.length;
};
打印队列中的元素和Stack 类里的一样
this.print = function(){
console.log(items.toString());
};
完成代码:
<script>
function Queue() {
//这里是属性和方法
var items = [];
// 向队列添加新元素。新的项只能添加到队列末尾
this.enqueue = function(element){
items.push(element);
console.log(items);Array(1)
};
// 从队列移除项
// 由于队列遵循先进先出原则,最先添加的项也是最先被移除的。
this.dequeue = function(){
return items.shift();
};
// 只有 enqueue 方法和 dequeue 方法可以添加和移除元素,
// 这样就确保了 Queue 类遵循先进先出原则
// 额外的辅助方法
// 返回队列最前面的项
this.front = function(){
return items[0];
};
// isEmpty 方法。如果队列为空,它会返回 true ,否则返回 false
// (注意这个方法和Stack 类里的一样)
this.isEmpty = function(){
return items.length == 0;
};
// size 方法也跟 Stack 类里的一样
// 判断数组里的元素个数
this.size = function(){
return items.length;
};
// 打印队列中的元素和Stack 类里的一样
this.print = function(){
console.log(items.toString());
};
}
var queue = new Queue();
queue.enqueue(3);//[3]
queue.enqueue(5);//[3,5]
// queue.dequeue();//移除第一个元素
console.log(queue.front());//3
console.log(queue.isEmpty());//false
console.log(queue.size());//2
queue.print();//3,5
</script>
- Queue 类和 Stack 类非常类似。唯一的区别是 dequeue 方法和 front 方法,这是由于先进先出和后进先出原则的不同所造成的。
- 使用 Queue 类
首先要做的是实例化我们刚刚创建的 Queue 类,然后就可以验证它为空(输出为 true ,因为我们还没有向队列添加任何元素):
function Queue() {
//这里是属性和方法
var items = [];
this.isEmpty = function(){
return items.length == 0;
};
}
var queue = new Queue();
console.log(queue.isEmpty());//true
完整代码:
function Queue() {
var items = [];
this.enqueue = function(element){
items.push(element);
console.log(items);
};//入队
this.dequeue = function(){
return items.shift();
};//出队
this.front = function(){
return items[0];
};//查看队头元素
this.isEmpty = function(){
return items.length == 0;
};//判断队列是否为空
this.size = function(){
return items.length;
};//队列大小
this.print = function(){
console.log(items.toString());
};//打印队列
}
var queue = new Queue();//声明队列的实例
console.log(queue.isEmpty());//false
queue.enqueue("John");//["John"]
queue.enqueue("Jack");//(2) ["John", "Jack"]
queue.enqueue("Camila");//(3) ["John", "Jack", "Camila"]
console.log(queue.size()); //输出3
console.log(queue.isEmpty()); //输出false
queue.dequeue();
queue.dequeue();
queue.print();//Camila
二.优先队列
一个现实的例子就是机场登机的顺序。
头等舱和商务舱乘客的优先级要高于经济舱乘客。在有些国家,老年人和孕妇(或带小孩的妇女)登机时也享有高于其他乘客的优先级
另一个现实中的例子是医院的(急诊科)候诊室。
医生会优先处理病情比较严重的患者。通常,护士会鉴别分类,根据患者病情的严重程度放号
- 实现一个优先队列,有两种选项:设置优先级,然后在正确的位置添加元素;或者用入列操作添加元素,然后按照优先级移除它们。
- 简单模拟排队
function Queue() {
var items = [];
this.enqueue = function(element){
items.push(element);
console.log(items);
};//入队
this.dequeue = function(){
return items.shift();
};//出队
this.front = function(){
return items[0];
};//查看队头元素
this.isEmpty = function(){
return items.length == 0;
};//判断队列是否为空
this.size = function(){
return items.length;
};//队列大小
this.clear = function () {
items = [];
};//清空队列
this.print = function(){
console.log(items.toString());
};//打印队列
}
var queue = new Queue();
console.log("队列是否为空: " + queue.isEmpty());
queue.enqueue('Mr.A');
queue.enqueue('Mr.B');
queue.enqueue('Mr.C');
console.log("当前队列:");
queue.print();
console.log("出队的人: " + queue.dequeue());
console.log("当前队列:");
queue.print();
console.log("清空队列:");
queue.clear();
queue.print();
- 优先队列自己的理解:在我们排队,来了一位拥有VIP会员卡的朋友,插到了队伍的最前面 。过了一会儿又来了一位拥有SVIP会员卡的朋友,插到了VIP的前面 (可能存在有优先级的队列,优先级高的人可以查到优先级低的人前面 )
注意:优先级大小、排序由自己设定。这里设置从小到大排序,优先级相同则先进先出
function PriorityQueue(){
var items = [];
function QueueElement(element,priority){
this.element = element;
this.priority = priority;
}
this.enqueue = function(element, priority){
var qe = new QueueElement(element, priority);
//若队列为空则直接将元素入列否则需要比较该元素与其他元素的优先级
if(this.isEmpty()){
items.push(qe);
}else{
var added = false;
//找出比要添加元素的优先级值更大的项目,就把新元素插入到它之前。
for(var i=0; i<items.length; i++){
//一旦找到priority值更大的元素就插入新元素并终止队列循环
if(qe.priority < items[i].priority){
items.splice(i,0,qe);
added = true;
break;
}
}
if(!added){
items.push(qe);
}
}
};
this.isEmpty = function(){
return items.length === 0;
};
this.print = function(){
var str = '';
for(var i=0; i<items.length; i++){
str += items[i].priority + ' ' + items[i].element + '\n'
}
console.log(str.toString());
}
}
/*test*/
var pq = new PriorityQueue();
pq.enqueue('John',2);
pq.enqueue('Jack',1);
pq.enqueue('Camila',1);
pq.print();
更改以下为“最大优先队列”
三.循环队列——击鼓传花
-
还有另一个修改版的队列实现,就是循环队列。
-
循环队列的一个例子就是击鼓传花游戏(Hot
Potato)。在这个游戏中,孩子们围成一个圆圈,把花尽快地传递给旁边的人。某一时刻传花停止,
这个时候花在谁手里,谁就退出圆圈结束游戏。重复这个过程,直到只剩一个孩子(胜者) -
击鼓传花(循环队列)代码:
function Queue() {
var items = [];
this.enqueue = function (ele) {
items.push(ele);
};//入队
this.dequeue = function () {
return items.shift();
};//出队
this.front = function () {
return items[0];
};//查看队头元素
this.isEmpty = function () {
return items.length === 0;
};//判断队列是否为空
this.size = function () {
return items.length;
};//队列大小
this.clear = function () {
items = [];
};//清空队列
this.print = function () {
console.log(items.toString());
};//打印队列
}
function hotPotato (nameList, num){//参数:表示人的数组,传花的频率
var queue = new Queue(); // {1}
for (var i=0; i<nameList.length; i++){
queue.enqueue(nameList[i]); // {2}初始化,把里面的名字全都加入队列,进入队列
}
var eliminated = '';//被淘汰的人
while (queue.size() > 1){ //只要队列至少还有两个人,就一直循环
for (var i=0; i<num; i++){//出队入队,模拟循环效果
queue.enqueue(queue.dequeue()); // {3}给定一个数字,然后迭代队列。从队列开头移除一项,再将其添加到队列末尾
}
eliminated = queue.dequeue();//清算// {4}一旦传递次数达到给定的数字,拿着花的那个人就被淘汰了(从队列中移除)
console.log(eliminated + '在击鼓传花游戏中被淘汰');
}
return queue.dequeue();// {5}
}
var names = ['John','Jack','Camila','Ingrid','Carl'];
var winner = hotPotato(names, 7);
console.log('胜利者:' + winner);//最终剩余一人
循环队列应用:约瑟夫环
循环队列应用:约瑟夫环(对象)
约瑟夫环:已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。
从编号为1的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去
例:N个人围城一桌(首位相连),约定从1报数,报到数为num的人出局,然后下一位又从1开始报,以此类推。最后留下的人获胜。
arr = ['alice','prime','William','Alexander','Matthew','Emma','Isabella'];
n=0;
start=0;
function circle(num) {
while (arr.length>1){
n+=1;//次数
start+=num-1;//下标
console.log("下标为:"+start,"此时的数组为:"+arr);
start%=arr.length;//如果下标超出数组的长度做取余处理
console.log('第'+n+'次删除的是:'+arr.splice(start,1));
}
console.log('winner:'+arr);
}
circle(3);//里面填入从哪个数字开始报数
**start+=num-1;**计算数组下标
**start%=arr.length;**取余数,计算每次需要删除的元素是什么
循环队列应用:约瑟夫环(连续数字)
function Queue() {
//items 是一个私有属性,只能被 Queue 函数访问,不能被外部访问,所以方法必须写在构造函数内,才能访问到 items 这个属性,
// 我们希望实例化对象只能访问在 Queue 函数内定义好的方法,实例化对象是不能访问这个属性的,
// 比如实例化了一个 queue 对象,queue.items 返回的结果是 undefined,
// 如果items能被外部访问到(比如你写成this.items = []),queue.items 就能访问到 items 这个属性,
// 因为我们是用数组来存储队列的值的(当然,不一定非要用数组来存储值),就可以通过数组的方式随意更改这个队列的值,
// 那么这个队列就不能叫队列了,你也可以从中间删除或添加值,破坏了队列先进先出的原则。
// 但是用构造函数实现的队列,如果实例化对象很多的话,那么创造的属性和方法的副本就太多了,
// 一个可行的方法是使用 es6 的 WeakMap 来实现队列,但扩展类无法继承私有属性,所以这个看实际需求来决定用哪种方式实现队列了
let items = [];
this.enqueue = function(element) { //向队列尾部添加一个(或多个)新的项
items.push(element);
};
this.dequeue = function() { //移除队列的第一项,并返回被移除的值
return items.shift();
};
this.size = function() { //返回队列包含的元素个数,与数组的length属性类似
return items.length;
};
}
function circle(n, m) {
if(n<=1 || m<1) {
console.log("you can't play Joseph's game. n must be bigger than 1, m must be bigger than 0");
return;
}
let queue = new Queue(); //实例化一个对象
for(let i=0;i<n;i++) { //将 0——n-1 的数字存入队列中
queue.enqueue(i);
}
while(queue.size() > 1) { //当队列中的项只剩一个时,循环结束
//从第 0 项开始,每循环一次,就从队列开头移除一项,再将其添加到队列末尾,
// 所以只需要移动 m-1 次就报数了 m 次了
for(let i=0;i<m-1;i++) {
queue.enqueue(queue.dequeue());
}
queue.dequeue(); //当报数为 m 时,移除该项
}
//打印最后一项,并且加 1,为了对应 1——n
console.log(queue.dequeue() + 1 + ' is the winner');
}
// let start = new Date().getTime();
circle(7,3);//n为连续的数字的个数,3为要循环到的删除的一项的序号
// let end = new Date().getTime();
// console.log('====' + (end - start) + '====');