队列是遵循先进先出(FIFO)原则的一组有序的项,队列在队尾添加新元素,从头部移除元素。最新添加的元素必须排在队列的末尾。
在现实生活中,最常见的队列的例子就是排队,比如在食堂排队打饭。排在第一位的人会先打到饭先离开,排在最后一位的人最后打到饭最后离开,以此类推直至所有排队的人都打完饭。
队列分为普通队列、双端队列、循环队列。
普通队列:
就是上面介绍到的一组有序的项,队列在队尾添加新元素,从顶部移除元素。最新添加的元素必须排在队列的末尾。
leecode官网动态图----队列:
以上可以看出队列是一种先进先出的数据结构。与栈不同的是,栈是后进先出(LIFO规则)的数据结构,见下图:
队列有以下的一些方法:
enqueue(element): 向队列尾部添加新的项
dequeue():移除队列的第一项(即排在队列最前面的项)并返回被移除的元素
peek():返回队列中第一项,该方法在其他语言中也叫做front方法。
isEmpty():如果队列中不包含任何元素,返回true,否则返回false
size():返回队列包含的元素个数,与数组的length属性类似
普通队列的具体实现:
// 普通队列的实现
class Queue {
constructor(){
this.count = 0; //队列大小
this.lowestCount = 0; //追踪队列首项位置
this.items = {} //存储队列中的元素
}
// 入队
enqueue(element){
this.items[this.count] = element;
this.count++
}
// 出队
dequeue(){
if(this.isEmpty()){
return undefined
}
const deletedElement = this.items[this.lowestCount] //存储队首项,最后return用
delete this.items[this.lowestCount] //删除队首项
this.lowestCount++ //队首项位置++
return deletedElement
}
// 查看队首项
peek(){
if(this.isEmpty()){
return undefined
}
return this.items[this.lowestCount]
}
// 查看队列的长度
size(){
return this.count-this.lowestCount
}
// 检查队列是否为空
isEmpty(){
return this.size() === 0
}
// 清空队列
clear(){
this.count=0
this.lowestCount=0
this.items=0
}
// 打印队列项
toString(){
if(this.isEmpty()){
return ''
}
// 声明变量存储队列项
let objString = `${this.items[this.lowestCount]}`
for(let i = this.lowestCount+1; i < this.count; i++){
objString = `${objString},${this.items[i]}`;
}
return objString
}
}
使用queue类(使用普通队列):
// 使用普通队列
const queue = new Queue()
console.log(queue.isEmpty()) // true
queue.enqueue('星期一,星期五')
queue.enqueue('星期二')
queue.enqueue('星期三')
console.log(queue.isEmpty()) //false
console.log(queue.size()) //3
console.log(queue.peek()) //星期一,星期五
console.log(queue.toString()) //星期一,星期五,星期二,星期三
出队演示:
queue.dequeue()
console.log(queue.toString()) //星期二,星期三
清空队列:
queue.clear()
console.log(queue.size()) //0
console.log(queue.isEmpty()) //true
双端队列:
与普通队列不同的是,普通队列只能从前端移除元素,从后端添加元素。而双端队列是一种允许我们同时从前端和后端添加和移出元素的特殊队列。遵循的是先进先出,后进先出的原则。
双端队列在现实生活中的例子有电影院、餐厅中排队的队伍等。比如一个刚买了票的人还需要再问一些简单的问题,就可以直接回到队伍的头部。另外在队伍末尾的人如果赶时间,他可以直接从队尾离开。
双端队列的具体实现:
对于双端队列的实现中,部分代码和普通队列相同,包括isEmpty、clear、size和toString. 由于双端队列允许在两端添加和移除元素,还会有以下几个方法:
addFront(element):在双端队列前端添加新的元素。
addBack(element):在双端队列后端添加新的元素(实现方法和普通队列里的enqueue方法相同)
removeFront():从双端队列前端移除第一个元素(实现方法和普通队列里的dequeue方法相同)
removeBack():从双端队列后端移除最后一个元素
peekFront():获取双端队列前端的第一个元素(实现方法和普通队列里的peek方法相同)
peekBack():获取双端队列后端的最后一个元素
代码实现:
class Deque{
constructor(){
this.count = 0; //队列大小
this.lowestCount = 0; //追踪队列首项位置
this.items = {} //存储队列中的元素
}
// 队尾入队
addBack(element){
this.items[this.count] = element;
this.count++
}
// 队首入队
addFront(element){
if(this.isEmpty()){ // 队列为空,直接添加元素
this.addBack(element)
}else if(this.lowestCount > 0) { // 首项位置 >0
this.lowestCount--
this.items[this.lowestCount] = element
}else{ // 首项位置 = 0
for(let i = this.count; i > 0; i--) {
this.items[i] = this.items[i-1];
}
this.count++
this.lowestCount = 0
this.items[0] = element
}
}
// 队首出队
removeFront(){
if(this.isEmpty()){
return undefined
}
const deletedFrontElement = this.items[this.lowestCount] //存储队首项,最后return用
delete this.items[this.lowestCount] //删除队首项
this.lowestCount++ //队首项位置++
return deletedFrontElement
}
// 队尾出队
removeBack(){
if(this.isEmpty()){
return undefined;
}
const deletedTailElement = this.items[this.count-1];
delete this.items[this.count-1];
this.count--;
return deletedTailElement;
}
// 查看队首项
peekFront(){
if(this.isEmpty()){
return undefined
}
return this.items[this.lowestCount]
}
// 查看队尾项
peekBack(){
if(this.isEmpty()){
return undefined
}
return this.items[this.count-1]
}
// 查看队列的长度
size(){
return this.count-this.lowestCount
}
// 检查队列是否为空
isEmpty(){
return this.size() === 0
}
// 清空队列
clear(){
this.count=0
this.lowestCount=0
this.items=0
}
// 打印队列项
toString(){
if(this.isEmpty()){
return ''
}
// 声明变量存储队列项
let objString = `${this.items[this.lowestCount]}`
for(let i = this.lowestCount+1; i < this.count; i++){
objString = `${objString},${this.items[i]}`;
}
return objString
}
}
使用deque类(使用双端队列):
// 使用双端队列
const deque = new Deque()
console.log(deque.isEmpty()) // true
deque.addFront('John') // John
deque.addBack('Jack') // John,Jack
deque.addFront('Lucy') // Lucy,John,Jack
deque.addBack('Simone') // Lucy,John,Jack,Simone
deque.addFront('Lisa') // Lisa,Lucy,John,Jack,Simone
console.log(deque.toString()) // Lisa,Lucy,John,Jack,Simone
图示:
去掉队列的队首和队尾项,再次查看队列:
deque.removeBack()
console.log(deque.toString()) // Lisa,Lucy,John,Jack
deque.removeFront()
console.log(deque.toString()) // Lucy,John,Jack
console.log(deque.peekFront()) // Lucy
console.log(deque.peekBack()) // Jack
双端队列的应用--回文检查器
回文:是正反都能读通的单词、词组、数或一系列字符的序列,例如madam或racecar。就可以使用双端队列来封装一个回文检查器,判断某个字符是否是回文。
// 使用双端队列来封装一个回文检查器。
function palindromeChecker(aString){
// 如果传入的数据是undefined、null 或空,直接返回false不继续往下执行判断
if(aString === undefined || aString === null || (aString !== null && aString.length === 0)){
return false
}
const deque = new Deque()
const lowerString = aString.toLocaleLowerCase().split(' ').join('') //对字符串进行格式处理成:全小写无空格的字符串
let isEqual = true
let firstChar,lastChar
// 把字符逐个添加到双端队列中
for (let i = 0; i < lowerString.length; i++) {
deque.addBack(lowerString.charAt(i)) //charAt是返回指定位置的字符
}
// 对队列进行首尾判断
while(deque.size() > 1 && isEqual) {
firstChar = deque.removeFront()
lastChar = deque.removeBack()
if(firstChar !== lastChar){
return false
}
}
return isEqual
}
// 输入字符串,检验是否是回文
console.log('ewfhwe',palindromeChecker('ewfhwe'))
console.log('a',palindromeChecker('a'))
console.log('aa',palindromeChecker('aa'))
console.log('kayak',palindromeChecker('kayak'))
console.log('level',palindromeChecker('level'))
console.log('Was it a car or a cat I saw',palindromeChecker('Was it a car or a cat I saw'))
循环队列:
由于队列经常被应用在计算机领域和我们的现实生活中,就出现了一些队列的修改版本,其中有一种就叫做循环队列。循环队列中的一个例子就是击鼓传花游戏。 这个游戏就是孩子们围成一个圆圈,把花尽快传递给旁边的人。某一时刻传花停止,这个时候花在谁手中,谁就退出圆圈。重复这个过程,直至只剩一个孩子(胜者)。
实现击鼓传花游戏:
// elementList是传入的元素数组,num是击鼓的次数
function hotPotato(elementList,num){
const queue = new Queue()
const deletedList = []
// 将元素数组传入队列中
for (let i = 0; i < elementList.length ; i++){
queue.enqueue(elementList[i])
}
// 循环队列:传递花的成员自动从队头排到队尾
while(queue.size() > 1 ){
for (let i = 0 ; i < num; i++){
queue.enqueue(queue.dequeue())
}
deletedList.push(queue.dequeue())
}
// 游戏结束:返回淘汰者名单和胜者
return {
deletedList: deletedList,
winner: queue.dequeue()
}
}
// 验证:
const elementList = ['John','Jack','Lucy','Simone','Lisa']
const result = hotPotato(elementList,7)
// 输出淘汰者名单
result.deletedList.forEach(name => {
console.log(`${name}在击鼓传花游戏中被淘汰`)
})
// 输出胜者名称
console.log(result.winner)
图示过程:
以上就是数据结构-队列的相关内容.