队列的基本概念
队列是遵循先进先出(FIFO,也称为先来先服务)原则的一组有序的项。队列在尾部添加新元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾。
队列的对应方法:
enqueue:向队列尾部添加一个或是多个新的项,类似于push
dequeue:移除当前位于队列顶部的第一项,并返回被移除的元素,类似于shift
peek:返回当前位于队列的第一项元素
isEmpty:判断是否为空
size:队列中数据的个数
clear:清空队列
toString:转换成字符串输出
迭代器接口功能
单向队列的自我实现
// 单向队列
class Queue {
constructor(){
this.count = 0; // 计数器
this.items = {}; // 存储数据的仓库
}
enqueue(...eles){ // 向队列尾部添加一个或是多个新的项,类似于push
for(let i = 0, len = eles.length; i < len; i++){
this.items[this.count++] = eles[i] // 对应元素赋值之后,计数器自增1
}
}
isEmpty(){ // 判断是否为空
return !this.count;
}
dequeue(){ // 移除当前位于队列顶部的第一项,并返回被移除的元素,类似于shift
if(this.isEmpty()){ // 如果为空则返回undefined
return undefined;
}else {
let result = this.items[0]; // 用变量存储当前位于第一个的元素
for(let i = 0; i < this.count - 1; i++){ // 进行覆盖操作
this.items[i] = this.items[i + 1] // 用后面的覆盖前面的
}
delete this.items[this.count - 1] // 因为是后面覆盖前面,最后会多一个,删掉即可
this.count--; // 相应计数器减1
return result; // 返回被移除的项
}
}
peek(){
if(this.isEmpty()){ // 如果为空
return undefined;
}else {
return this.items[0]; // 返回第一项
}
}
size(){
return this.count;
}
toString(){ // 和栈的toString一模一样
if(this.isEmpty()){
return "";
}else {
let objString = "";
for(let i = 0; i < this.count; i++){
objString = `${objString},${this.items[i]}`
}
return objString.slice(1);
}
}
}
let queue = new Queue();
queue.enqueue("hello");
queue.enqueue("world");
queue.enqueue("你好","世界");
console.log(queue)
双向队列的自我实现
双端队列(deque,或称double-ended queue)是一种允许我们同时从前端和后端添加和移除元素的特殊队列。
在计算机科学中,双端队列的一个常见应用是存储一系列的撤销操作。每当用户在软件中进行了一个操作,该操作会被存在一个双端队列中(就像在一个栈里)。当用户点击撤销按钮时,该操作会被从双端队列中弹出,表示它被从后面移除了。在进行了预先定义的一定数量的操作后,最先进行的操作会被从双端队列的前端移除。
由于双端队列同时遵守了先进先出和后进先出原则,可以说它是把队列和栈相结合的一种数据结构。
// 双向队列
class DBQueue {
constructor(){
this.count = 0; // 计数器
this.items = {}; // 存储数据的仓库
}
addBack(...eles){ // 向队列尾部添加一个或是多个新的项,类似于push
for(let i = 0, len = eles.length; i < len; i++){
this.items[this.count++] = eles[i] // 对应元素赋值之后,计数器自增1
}
}
isEmpty(){ // 判断是否为空
return !this.count;
}
removeFront(){ // 移除当前位于队列顶部的第一项,并返回被移除的元素,类似于shift
if(this.isEmpty()){ // 如果为空则返回undefined
return undefined;
}else {
let result = this.items[0]; // 用变量存储当前位于第一个的元素
for(let i = 0; i < this.count - 1; i++){ // 进行覆盖操作
this.items[i] = this.items[i + 1] // 用后面的覆盖前面的
}
delete this.items[this.count - 1] // 因为是后面覆盖前面,最后会多一个,删掉即可
this.count--; // 相应计数器减1
return result; // 返回被移除的项
}
}
addFront(...eles){ // 在队列头部增加一项,unshift
// 之所以写两个循环是因为,第一次往队列头部新增1个数,所有的原来的数据相应地往后挪1位,
// 第二次又往队列头部新增新1个数,所有的原来队列中的数据又要相应地往后挪1位,依次循环
for(let e = 0,len = eles.length;e < len; e++){
for(let i = this.count; i > 0; i--){
// count是计数器,计数器会比下标多1,所以i比原先的数据还要多1位
this.items[i] = this.items[i - 1]
// 由上面多1位可知,这里this.items[i]是空值,this.items[i - 1]才是原来最后1位的值
}
this.items[0] = eles[e]
this.count++
}
}
removeBack(){ // 在队列尾部删除一项,pop
if(this.isEmpty()){
return undefined
}else{
this.count-- // 自减1位之后才是最后1位的下标
let result = this.items[this.count] // 将最后一位保存下来
delete this.items[this.count] // 删除最后一位
return result // 并且将删除的最后一位返回出来
}
}
peekFront(){ // 返回当前队列最前一位
if(this.isEmpty()){
return undefined
}else{
return this.items[0]
}
}
peekBack(){ // 返回当前队列最后一位
if(this.isEmpty()){
return undefined
}else{
return this.items[this.count - 1]
}
}
size(){
return this.count;
}
toString(){ // 和栈的toString一模一样
if(this.isEmpty()){
return "";
}else {
let objString = "";
for(let i = 0; i < this.count; i++){
objString = `${objString},${this.items[i]}`
}
return objString.slice(1);
}
}
[Symbol.iterator](){ // 手动添加一个迭代器接口
let self = this // 先保存好当前的this
let index = 0 // 定义一个索引
return {
next(){
if(index < self.count){ // 如果没有遍历结束,则一个个返回当前值
return {
value:self.items[index++], // 运用了闭包
done:false // false表示没有遍历完成
}
}else{ // 否则如果变量结束,则返回undefined
return {
value:undefined,
done:true // true表示遍历结束
}
}
}
}
}
}
let dbqueue = new DBQueue()
队列结构的基本应用
击鼓传花
class DBQueue{
constructor(){
this.count = 0 //计数器
this.items = {} //队列的数据仓库
}
addBack(...eles){ //在队列尾部增加一项,push
for(let i = 0, len = eles.length; i < len; i++){
this.items[this.count] = eles[i] //采用计数器当前值(自然数)为下标key,存储实际数据
this.count++ //每次push进一个数据,计数器自增1
}
}
removeFront(){ //移除队列当前最前面一项,shift
if(this.isEmpty()){
return undefined
}else{
let result = this.items[0] //先将队列最前面的那一项保存下来,因为移除了最前面一项,队列后面剩下的所有项都会相应地往前挪一位
for(let i = 0; i < this.count - 1; i++){
this.items[i] = this.items[i + 1] //循环将剩下队列的每一项向前挪一位,就是将后一项值赋值给前一项
}
delete this.items[this.count - 1] //当剩下所有的项都往前挪了一位,最后一位会赋值给倒数第二位,这样就有两个是相同的,所以删除最后一位
this.count-- //每次删除一位,相应的计数器要自减1
return result //返回我们一开始保存好的队列要删除的第一位
}
}
addFront(...eles){ //在队列头部增加一项,unshift
//之所以写两个循环是因为,第一次往队列里新增1个数,所有的原来的数据相应地往后挪1位,
//第二次又往队列里新增新1个数,所有的原来队列中的数据又要相应地往后挪1位,依次循环
for(let e = 0,len = eles.length;e < len; e++){
for(let i = this.count; i > 0; i--){ //count是计数器,计数器会比下标多1,所以i比原先的数据还要多1位
this.items[i] = this.items[i - 1] //由上面多1位可知,这里this.items[i]是空值,this.items[i - 1]才是原来最后1位的值
}
this.items[0] = eles[e]
this.count++
}
}
removeBack(){ //在队列尾部删除一项,pop
if(this.isEmpty()){
return undefined
}else{
this.count-- //自减1位之后才是最后1位的下标
let result = this.items[this.count] //将最后一位保存下来
delete this.items[this.count] //删除最后一位
return result //并且将删除的最后一位返回出来
}
}
peekFront(){ //返回当前队列最前一位
if(this.isEmpty()){
return undefined
}else{
return this.items[0]
}
}
peekBack(){ //返回当前队列最后一位
if(this.isEmpty()){
return undefined
}else{
return this.items[this.count - 1]
}
}
isEmpty(){
return !this.count
}
size(){
return this.count
}
toString(){
if(this.isEmpty()){
return ""
}else{
let objString = ""
for(let i = 0; i < this.count; i++){
objString = `${objString},${this.items[i]}`
}
return objString.slice(1)
}
}
[Symbol.iterator](){ //手动添加一个迭代器接口
let self = this //先保存好当前的this
let index = 0 //定义一个索引
return {
next(){
if(index < self.count){ //如果没有遍历结束,则一个个返回当前值
return {
value:self.items[index++], //运用了闭包
done:false //false表示没有遍历完成
}
}else{ //否则如果变量结束,则返回undefined
return {
value:undefined,
done:true //true表示遍历结束
}
}
}
}
}
}
/*
* 从人不动花动,转变为花不动人动,两者都是一样的
*
* */
function chuanhua(player,num){
const xingcunzhe = new DBQueue() //新建一个幸存者双向队列
const chujuzhe = new DBQueue() //新建一个出局者双向队列
for(let i = 0;i < player.length; i++){
xingcunzhe.addBack(player[i])
}
while(xingcunzhe.size() > 1){ //只要幸存者还大于1个人,游戏就继续
for (let i = 0; i < num; i++){
xingcunzhe.addBack(xingcunzhe.removeFront()) //模拟花不动人动,就是循环将队列最前面的人,删除并添加到队列最后面
}
chujuzhe.addBack(xingcunzhe.removeFront()) //每次循环完一圈,就从幸存者队列中删除最后一个,添加到出局者队列的最后一个
console.log(chujuzhe)
}
return {
fire:chujuzhe,
winner:xingcunzhe.removeFront()
}
}
let xingcunzhe = ["万章","哒哒哒","努力生长","阿仁","童同学","小飞","长安忆"]
function randomInt(min,max){ // 随机时间
return Math.floor(Math.random()*(max-min)+min)
}
chuanhua(xingcunzhe,randomInt(30,45))
回文检查器
class DBQueue{
constructor(){
this.count = 0 //计数器
this.items = {} //队列的数据仓库
}
addBack(...eles){ //在队列尾部增加一项,push
for(let i = 0, len = eles.length; i < len; i++){
this.items[this.count] = eles[i] //采用计数器当前值(自然数)为下标key,存储实际数据
this.count++ //每次push进一个数据,计数器自增1
}
}
removeFront(){ //移除队列当前最前面一项,shift
if(this.isEmpty()){
return undefined
}else{
let result = this.items[0] //先将队列最前面的那一项保存下来,因为移除了最前面一项,队列后面剩下的所有项都会相应地往前挪一位
for(let i = 0; i < this.count - 1; i++){
this.items[i] = this.items[i + 1] //循环将剩下队列的每一项向前挪一位,就是将后一项值赋值给前一项
}
delete this.items[this.count - 1] //当剩下所有的项都往前挪了一位,最后一位会赋值给倒数第二位,这样就有两个是相同的,所以删除最后一位
this.count-- //每次删除一位,相应的计数器要自减1
return result //返回我们一开始保存好的队列要删除的第一位
}
}
addFront(...eles){ //在队列头部增加一项,unshift
//之所以写两个循环是因为,第一次往队列里新增1个数,所有的原来的数据相应地往后挪1位,
//第二次又往队列里新增新1个数,所有的原来队列中的数据又要相应地往后挪1位,依次循环
for(let e = 0,len = eles.length;e < len; e++){
for(let i = this.count; i > 0; i--){ //count是计数器,计数器会比下标多1,所以i比原先的数据还要多1位
this.items[i] = this.items[i - 1] //由上面多1位可知,这里this.items[i]是空值,this.items[i - 1]才是原来最后1位的值
}
this.items[0] = eles[e]
this.count++
}
}
removeBack(){ //在队列尾部删除一项,pop
if(this.isEmpty()){
return undefined
}else{
this.count-- //自减1位之后才是最后1位的下标
let result = this.items[this.count] //将最后一位保存下来
delete this.items[this.count] //删除最后一位
return result //并且将删除的最后一位返回出来
}
}
peekFront(){ //返回当前队列最前一位
if(this.isEmpty()){
return undefined
}else{
return this.items[0]
}
}
peekBack(){ //返回当前队列最后一位
if(this.isEmpty()){
return undefined
}else{
return this.items[this.count - 1]
}
}
isEmpty(){
return !this.count
}
size(){
return this.count
}
toString(){
if(this.isEmpty()){
return ""
}else{
let objString = ""
for(let i = 0; i < this.count; i++){
objString = `${objString},${this.items[i]}`
}
return objString.slice(1)
}
}
[Symbol.iterator](){ //手动添加一个迭代器接口
let self = this //先保存好当前的this
let index = 0 //定义一个索引
return {
next(){
if(index < self.count){ //如果没有遍历结束,则一个个返回当前值
return {
value:self.items[index++], //运用了闭包
done:false //false表示没有遍历完成
}
}else{ //否则如果变量结束,则返回undefined
return {
value:undefined,
done:true //true表示遍历结束
}
}
}
}
}
}
/*
* 上海自来水来自海上
* 本日飞机飞日本
* */
function palindromeChecker(testString){
if( // 先做边界限制
testString === undefined ||
testString === null ||
(testString !== null && testString.length === 0)
){
return false
}
const dbQueue = new DBQueue()
const lowerString = testString.toLocaleLowerCase().split(" ").join("")
// 统一设置大小写,split去除中间的空格
let isPalindrome = true // 先默认是回文
for(let i = 0; i < lowerString.length; i++){
dbQueue.addBack(lowerString.charAt(i)) // 循环将返回指定索引处的字符添加到双向队列中的尾部
}
while(dbQueue.size() > 1 && isPalindrome){ // 当队列的数据内容大于1并且是回文的时候,就一直循环比对
let firstChar = dbQueue.removeFront() // 从前面开始取
let lastChar = dbQueue.removeBack() // 从后面开始取
isPalindrome = firstChar === lastChar // 将对比的布尔值赋值给初始默认值
}
return isPalindrome // 最后返回对比结果
}
console.log(palindromeChecker("上海自来水来自海上"))
console.log(palindromeChecker("前任只认钱"))
不使用队列的回文检查器
function palindromeChecker(testString){
return testString === testString.toLocaleLowerCase().split("").reverse().join("")
}
console.log(palindromeChecker("上海自来水来自海上"))
console.log(palindromeChecker("前任只认钱"))
额外拓展:求最大公约数算法(不是使用队列)
// x:数字1
// y:数字2
// 公约数:两个数都能整除的最大数字
// 版本1
function maxCommonDivisor(n1,n2){ // 两个数字
if((n1 - n2) > 0){ // 先比较传入的两个数的大小
[n1,n2] = [n2,n1] // 当前一个数大于后一个数的时候,用解构赋值将两者的值进行对换
}
let commonDivisor = 0 // 先定义一个最大公约数,默认值为0
for(let i = 0; i <= n1; i++){
if(n1 % i === 0 && n2 % i === 0){ // 循环比对能够被整除的最大数
commonDivisor = i
}
}
return commonDivisor
}
//版本2:更损相减法
// 状态1:给定的两个正整数,判断是否是偶数,是的话,用2约简
// 状态2:用大数减去小数,然后得出的差与小数进行比较,比较出大小后用大的减去小的,得出的差再与小数比较大小,比较完之后再用大数减去小数,直到得到的减数与差相等
// 最后约掉的2与等数的乘积就是公约数
function maxCommonDivisor2(n1,n2){ // 两个数字
let count = 1 // 先定义一个约除2的计数器
// 状态1的情况
while(n1 % 2 === 0 && n2 % 2 === 0){ // 只要他们都是偶数就进行这个循环
n1 = n1 / 2
n2 = n2 / 2
count*=2 // 每当他们都约除了2,计数器就乘个2
}
// 状态2的情况
if((n1 - n2) > 0){ // 先比较传入的两个数的大小
[n1,n2] = [n2,n1] // 当前一个数大于后一个数的时候,用结构赋值将两者的值进行对换
}
let poor = n2 - n1 // 求差值
while(poor !== n1){ // 当差值与减数不相等的时候就一直循环
if(poor < n1){ // 当差值小于减数时
[n2,n1] = [n1,poor] // 两数进行调换,减数变成大数,差值变成小数
}else{
[n2,n1] = [poor,n1] // 否则,差值变成大数,减数变成小数
}
poor = n2 - n1 // 两数相减得到新的差值
}
return count*poor
}