数据结构-队列

Posted newbase

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构-队列相关的知识,希望对你有一定的参考价值。

数据结构

队列&栈

队列---先入先出(FIFO)的数据结构

技术图片

队列是典型的 FIFO 数据结构。插入(insert)操作也称作入队(enqueue),新元素始终被添加在队列的末尾tail)。 删除(delete)操作也被称为出队(dequeue)。 你只能移除第一个元素head)。

用数组实现循环队列(取模)

下面我用golang来实现循环队列,但需要注意,程序中tailIndex所指引的是最后一个插入的元素(即真实的队尾元素),而非插入元素的下一个位置(许多其他实现采用,详见B站视频)。这有很大区别,此时 队尾与队首的关系为:

tailIndex = (headIndex+count?1) % capacity

新增元素:

tailIndex = (tailIndex+1) % capacity

---> tailIndex = (headIndex+count) % capacity

删除元素:

headIndex = (headIndex+1) % capacity

下面图表示队列容量为5,长度为3实现时head与tail所指位置:

技术图片

程序利用了取模运算,共定义了四个属性:数组;队首索引;队列长度;队列容量

/*

   enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
   deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
   MyCircularQueue(k): 构造器,设置队列长度为 k 。
   Front: 从队首获取元素。如果队列为空,返回 -1 。
   Rear: 获取队尾元素。如果队列为空,返回 -1 。
   isEmpty(): 检查循环队列是否为空。
   isFull(): 检查循环队列是否已满。

*/

type MyCircularQueue struct {
	queue     []int // 一个固定大小的数组,用于保存循环队列的元素
	headIndex int   // 一个整数,保存队首 head 的索引
	count     int   // 循环队列当前的长度,即循环队列中的元素数量。
	// tailIndex = (headIndex+count?1) % capacity , 因此不需要队尾属性
	capacity  int   // 循环队列的容量,即队列中最多可以容纳的元素数量。
}

/** Initialize your data structure here. Set the size of the queue to be k. */
func Constructor(k int) MyCircularQueue {
	return MyCircularQueue{
		queue:     make([]int, k, k),
		headIndex: 0,
		count:     0,
		capacity:  k,
	}

}

/** Insert an element into the circular queue. Return true if the operation is successful. */
// 向循环队列插入一个元素。如果成功插入则返回真
func (m *MyCircularQueue) EnQueue(value int) bool {
	if m.IsFull() {
		return false
	}
	// 既定公式,令队尾+1 = value
	/*
	这里需要注意,是令队尾的下一个元素 = value
	即: m.queue[(m.headIndex+m.count-1+1)%m.capacity] = value
	*/
	m.queue[(m.headIndex+m.count)%m.capacity] = value
	m.count++
	return true

}

/** Delete an element from the circular queue. Return true if the operation is successful. */
// 从循环队列中删除一个元素。如果成功删除则返回真
func (m *MyCircularQueue) DeQueue() bool {
	if m.IsEmpty() {
		return false
	}
	m.headIndex = (m.headIndex + 1) % m.capacity
	m.count--
	return true
}

/** Get the front item from the queue. */
// 从队首获取元素。如果队列为空,返回 -1
func (m *MyCircularQueue) Front() int {
	if m.IsEmpty() {
		return -1
	}
	return m.queue[m.headIndex]
}

/** Get the last item from the queue. */
// 从队尾获取元素。如果队列为空,则返回 -1
func (m *MyCircularQueue) Rear() int {
	if m.IsEmpty() {
		return -1
	}
	return m.queue[(m.headIndex+m.count-1)%m.capacity]

}

/** Checks whether the circular queue is empty or not. */
// 检查循环队列是否为空
func (m *MyCircularQueue) IsEmpty() bool {
	return m.count == 0
}

/** Checks whether the circular queue is full or not. */
// 检查循环队列是否已满
func (m *MyCircularQueue) IsFull() bool {
	return m.count == m.capacity
}

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * obj := Constructor(k);
 * param_1 := obj.EnQueue(value);
 * param_2 := obj.DeQueue();
 * param_3 := obj.Front();
 * param_4 := obj.Rear();
 * param_5 := obj.IsEmpty();
 * param_6 := obj.IsFull();
 */

用数组实现循环队列(size)

这种方法略微取巧,不需要取模运算,但是实际运算速度也差不太多,还不容易理解.....

技术图片

判空:head == tail
判满:tail + 1 == head,数组越界时需要转换

有两点需要额外注意:

  • head或tail的数组越界转换问题:在移动过程中,若是越界则需要转换到有效下标值处
  • 访问队尾元素时的越界问题:队尾元素的下标是tail- 1,因此若是tail=0,则需要进行有效下标转换

程序共定义了四个属性:数组;队首索引;队尾索引;队列长度(多加一个用于给tail指针站位)

直接上代码:

/*
   MyCircularQueue(k): 构造器,设置队列长度为 k 。
   enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
   deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
   Front: 从队首获取元素。如果队列为空,返回 -1 。
   Rear: 获取队尾元素。如果队列为空,返回 -1 。
   isEmpty(): 检查循环队列是否为空。
   isFull(): 检查循环队列是否已满。
*/

// head为序列元素的上标,即队首,tail为序列的下标,即队尾,size为数组长度
type MyCircularQueue struct {
   data  []int
   head  int
   tail  int
   size  int
}


/** Initialize your data structure here. Set the size of the queue to be k. */
func Constructor(k int) MyCircularQueue {
   return MyCircularQueue{
      data: make([]int, k + 1, k + 1), // 确定data长度,容量
      head: 0,
      tail: 0,
      size: k + 1, // 多加一个用于给tail指针站位
   }
}



/** Insert an element into the circular queue. Return true if the operation is successful. */
// 向循环队列插入一个元素。如果成功插入则返回真
func (m *MyCircularQueue) EnQueue(value int) bool {
   // 如果序列已经满了,返回false
   if m.IsFull() {
      return false
   }
   // 向数组起始位置插入value,下标+1
   m.data[m.tail] = value
   m.tail++
   // 如果下标达到数组最后一个元素位置(等于size长度,这个位置是为tail准备的,不存储元素)下标归0 ---> 开始循环
   if m.tail == m.size {
      m.tail = 0
   }
   return true
}


/** Delete an element from the circular queue. Return true if the operation is successful. */
// 从循环队列中删除一个元素。如果成功删除则返回真。
func (m *MyCircularQueue) DeQueue() bool {
   // 如果队列是空的,返回false
   if m.IsEmpty() {
      return false
   }
   // 序列 head+1,如果到头了就归0 ---> 循环 <原理同上>
   m.head++
   if m.head == m.size {
      m.head = 0
   }
   return true
}


/** Get the front item from the queue. */
// 从队首获取元素。如果队列为空,返回 -1
func (m *MyCircularQueue) Front() int {
   if m.IsEmpty() {
      return -1
   }
   return m.data[m.head]

}


/** Get the last item from the queue. */
// 获取队尾元素。如果队列为空,返回 -1
func (m *MyCircularQueue) Rear() int {
   if m.IsEmpty() {
      return -1
   }
   // 边界问题,tail独占一个空间,因此要取的index为tail前一个位置
   lastIndex := m.tail - 1
   if lastIndex < 0 {
      lastIndex = m.size - 1
   }
   return m.data[lastIndex]

}


/** Checks whether the circular queue is empty or not. */
// 检查队列是否为空
func (m *MyCircularQueue) IsEmpty() bool {
   // 只要相等,队列即为空
   return m.head == m.tail
}


/** Checks whether the circular queue is full or not. */
// 检查队列是否已满
func (m *MyCircularQueue) IsFull() bool {
    // tail + 1 == head 时队列已满
   next := m.tail + 1
    // 越界问题处理
   if next == m.size {
      next = 0
   }
   return next == m.head
}


/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * obj := Constructor(k);
 * param_1 := obj.EnQueue(value);
 * param_2 := obj.DeQueue();
 * param_3 := obj.Front();
 * param_4 := obj.Rear();
 * param_5 := obj.IsEmpty();
 * param_6 := obj.IsFull();
 */

队列和广度优先搜索(BFS)

待定吧,慢慢来


以上是关于数据结构-队列的主要内容,如果未能解决你的问题,请参考以下文章

perl中的队列

IPC System V 消息队列 - 发送一个数组块

JDK常用数据结构

完全下载文件时,将下载的文件从一个片段传递到另一个片段

RocketMQ - 如何用死信队列解决消费者异常

rabbitmq - 不会获取队列中的所有消息