队列

队列的定义

队列是一种特殊的线性表,只允许在队列的头部进行删除元素,队列的尾部添加元素(先进先出)

queue1.png

左侧是队列的头部,右侧是队列的尾部,元素想进入队列,只能从尾部进入;想出队列,也只能从头部出去。


queue2.png

就像刷卡进地铁,只能从队尾一个个去排队,从最前面刷完卡就走了

队列的实现

跟栈一样,用数组来实现队列

队列的方法:

  • enqueue: 从队列尾部添加一个元素
  • dequeue: 从队列头部删除一个元素
  • head: 返回队列头部的元素,不是删除
  • tail: 返回队列尾部的元素
  • size: 返回队列的大小
  • clear: 清空队列
  • isEmpty: 判断队列是否为空
function Queue () {
  let queue = [];
  this.enqueue = function (item) {
    queue.push(item);
  }
  this.dequeue = function () {
    return queue.shift();
  }
  this.head = function () {
    return queue[0];
  }
  this.tail = function () {
    return queue[queue.length - 1];
  }
  this.size = function () {
    return queue.length;
  }
  this.clear = function () {
    queue = [];
  }
  this.isEmpty = function () {
    return queue.length === 0;
  }
}

队列的练习

练习一:约瑟夫环

有一个数组a[100]存放0--99;要求每隔两个数删掉一个数,到末尾时循环至开头继续进行,求最后一个被删掉的数。

思路分析:

所谓每隔两个数删掉一个数,假设有三个元素就删掉第三个,假设有六个元素就删掉第三个和第六个。。。由此可见就是删掉所在位置是3的倍数的元素

如果只删除一轮,用数组实现还可以,现在要求到末尾时循环至开头继续删除,用数组就很麻烦了,下标不好控制

那如何用队列来实现呢?

  • 先循环数组把元素存进队列中
  • 定义一个计数变量,用来计算是不是3的倍数
  • 用while循环数组,直到队列大小是1为止
  • 每次删除队首元素,计数变量+1,如果该元素所在位置不是3的倍数,再从队尾添加进去。这样一轮删除过后,队列里面就是剩下的没被删除的元素可以进行下一轮删除。
function delRing (arr) {
  const queue = new Queue();
  // 先把数组存入队列
  for (let i = 0; i < arr.length; i++) {
    queue.enqueue(arr[i]);
  }
  let index = 0; // 计数
  while(queue.size() !== 1) { // 只要队列长度不为1就继续循环
    const item = queue.dequeue(); // 从队列头部删除元素
    index++;
    if (index % 3 !== 0) { // 如果元素不能被3整除,就再次存入队列
      queue.enqueue(item);
    }
  }
  return queue.head();
}
const arr = [];
for (let i = 0; i < 100; i++) {
  arr[i] = i;
}
console.log(delRing(arr));

练习二:斐波那契数列

使用队列计算斐波那契数列的第n项

思路分析:

斐波那契数列指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)
  • 首先,前两项是已知的,先把它们放进队列
  • 从第三项开始,值就是前两项的和。先从队列首部删除一个元素这是第一个值,再使用head()方法获得队列头部的元素这是第二个值,把这两个值相加就是第三项的值,把这个值从队列尾部加进队列(为什么第二次是用head()方法而不是dequeue(), 因为我们每次都是取两个值进行运算,如果使用dequeue()方法删除,下次计算的时候就只有一个值没法进行运算了)
  • 因为我们每次都是删除一个值,再加进一个值,所以队列里面始终有两个元素。第二个元素就是第n项的值
function fibonacci (n) {
  const queue = new Queue();
  // 先把前两个元素放进队列
  queue.enqueue(1);
  queue.enqueue(1);
  for (let i = 2; i < n; i++) { // 因为前两个元素已知且已经放进队列,所以i从2开始
    const item1 = queue.dequeue(); // 第一个元素删除
    const item2 = queue.head(); // 第二个元素只是知道值就可以,不用删除,因为这个元素还要作为下一次计算的第一个值
    const sum = item1 + item2;
    queue.enqueue(sum); // 计算结果放进队列
  }
  return queue.tail(); // 尾部元素即是第n个元素的值
}
console.log(fibonacci(10));

练习三:用队列实现栈

用两个队列实现一个栈

思路分析:

主要记住队列是先进先出,栈是后进先出。

定义两个队列,queue1和queue2

  • push方法:由于是两个队列,添加元素的时候要明确具体向哪个队列中添加。如果两个队列都为空,默认向queue1中添加;否则向不空的队列添加
  • pop方法:对于栈来说,是删除栈顶元素,对于队列,就是队尾元素。由于队列只能从队首删除元素,所以要一直循环操作,删除队首元素并放进空队列中直到队列剩下一个元素为止,这个元素就是要删除的栈顶元素。删除并返回这个元素
  • top方法:返回栈顶元素,也就是队列的队尾元素。找到不为空的队列,返回队尾元素即可

可以发现每进行一次pop操作,空队列和不为空的队列就要做一次元素的转移。原本queue1不为空,pop操作完后就变成空的了。所以我们再定义两个变量,一个指向非空队列,一个指向空队列。

const Queue = require('./myQueue.js');
function QueueStack () {
  const queue1 = new Queue();
  const queue2 = new Queue();
  let dataQueue = null;  // 放数据的队列
  let emptyQueue = null; // 空队列,备份使用

  function initQueue () { // 确认放数据的队列和空队列的指向
    if (queue1.isEmpty() && queue2.isEmpty()) { // 如果都为空,默认放数据的队列指向queue1
      dataQueue = queue1;
      emptyQueue = queue2;
    } else if (queue1.isEmpty()) {
      dataQueue = queue2;
      emptyQueue = queue1;
    } else if (queue2.isEmpty()) {
      dataQueue = queue1;
      emptyQueue = queue2;
    }
  }

  this.push = function (item) {
    initQueue();
    dataQueue.enqueue(item);
  }
  
  this.pop = function () {
    initQueue();
    while(dataQueue.size() !== 1) {
      const item = dataQueue.dequeue();
      emptyQueue.enqueue(item);
    }
    return dataQueue.dequeue();
  }

  this.top = function () {
    initQueue();
    return dataQueue.tail();
  }
}
const qStack = new QueueStack();
qStack.push(1);
qStack.push(2);
qStack.push(4);
console.log(qStack.top());   // 栈顶是 4
console.log(qStack.pop());   // 移除 4
console.log(qStack.top());   // 栈顶变成 2
console.log(qStack.pop());   // 移除 2
console.log(qStack.pop());

练习四:打印杨辉三角

使用队列打印出杨辉三角的前n行,n >= 1

思路分析:

杨辉三角示意图:


yanghui.jpeg

杨辉三角中的每一行,都依赖于上一行。假设在队列里存储第n-1行的数据,输出第n行时,只需要将队列里的数据依次出队列,进行计算得到下一行的数值并将计算所得放入到队列中。

计算方式: f[i][j] = f[i-1][j-1]+f[i-1][j], i代表行数,j代表一行的第几个数,如果j=0或者j===i, 则f[i][j] = 1;

  • 定义一个字符串变量来存储结果,默认是1,存储第一行的结果
  • 把1存入队列,以便后续计算
  • 我们是将第n-1行的数据依次取出进行计算,然后再将结果存入队列。所以这时候队列中是存的两行的数据,需要区分到哪个位置为止停止计算也就是标记一下第n-1行的数据什么时候结束。这里我们在一行的数据结尾再存入一个0作为区分
function printYanghui (n) {
  if (n < 1) {
    console.error('n必须大于等于1');
    return;
  }
  let line = '1';
  const queue = new Queue();
  queue.enqueue(1);
  queue.enqueue(0);

  for (let i = 1; i < n; i++) { // 第一行数据1已经默认存储了,所以i从1开始
    line += '\n'; // 换行,每轮循环代表一行数据
    line += '1 '; // f[i][0] = 1
    queue.enqueue(1)
    while(queue.head() !== 0) { // 队列头部元素是0代表这行元素结束了
      // 取出n-1行两个元素进行计算,并将结果存入队列
      const item1 = queue.dequeue(); // 第一个元素删除
      const item2 = queue.head();    // 第二个元素取出不删除,因为它还要参与第n行下个元素的计算
      const res = item1 + item2;
      line += `${res} `;
      queue.enqueue(res);
    }
    queue.dequeue(); // 把第n-1行的0删除
    queue.enqueue(0); // 第n行末尾添加0
  }
  return line;
}
console.log(printYanghui(10));

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容