Go数据结构与算法—队列
Posted 小圣.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go数据结构与算法—队列相关的知识,希望对你有一定的参考价值。
介绍
队列(queue),是一种特殊的线性表。队列只允许在一端插入数据,另一端取出数据,并且队列中的数据遵循先进先出(First In First Our, FIFO)的规则。其中取出数据的一端称为队头,插入数据的一端称为队尾。
队列可以分为:顺序队列、循环队列、链式队列。
顺序队列
type Queue struct {
MaxSize int
Front int
Rear int
Element []int
}
// 初始化队列
func (q *Queue) initQueue(maxSize int) {
q.MaxSize = maxSize
q.Element = make([]int, maxSize)
q.Front = -1
q.Rear = -1
}
// 判断队列是否为空
func (q *Queue) isEmpty() bool {
flag := false
if q.Front == q.Rear {
flag = true
}
return flag
}
// 向队列中添加一个元素
func (q *Queue) add(data int) (err error) {
// 判断满了
if q.Rear == q.MaxSize-1 {
err = errors.New("队列已满,无法添加!")
return err
}
q.Rear++
q.Element[q.Rear] = data
return
}
// 获取队列的一个元素
func (q *Queue) poll() (data int, err error) {
if q.isEmpty() {
err = errors.New("the queue is empty")
return
}
q.Front++
data = q.Element[q.Front]
return data, err
}
// 查看队列的所有元素
func (q *Queue) list() {
for i := q.Front + 1; i < len(q.Element)-1; i++ {
fmt.Printf("element[%d]:%d\\n", i, q.Element[i])
}
}
func main() {
queue := Queue{}
var key int
var value int
var size int
for {
fmt.Println("0. 初始化队列")
fmt.Println("1. 添加数据到队列")
fmt.Println("2. 从队列中获取数据")
fmt.Println("3. 显示队列数据")
fmt.Println("4. 退出程序")
fmt.Println("请选择(0-4):")
fmt.Scanf("%d\\n", &key)
switch key {
case 0:
fmt.Println("请输入初始化队列的容量")
fmt.Scanf("%d\\n", &size)
queue.initQueue(size)
case 1:
fmt.Println("请输入要添加的数据:")
fmt.Scanf("%d\\n", &value)
err := queue.add(value)
if err != nil {
fmt.Println(err)
return
}
case 2:
data, err := queue.poll()
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("从队列中取出的数据为: %d\\n", data)
case 3:
queue.list()
case 4:
os.Exit(0)
}
}
}
顺序队列会产生假溢出的现象,因为顺序队列的存储单元没有重复使用的机制。所以顺序队列的空间利用率也不是太高。解决的办法就是将顺序队列设计成循环结构。
循环队列
循环队列在逻辑上将队列的首尾进行相连。这样,被取出数据的位置在下一轮循环中可以继续使用,提高了队列的空间利用率。
以下几点需要注意:
- 队头和队尾下标都是从0开始的,为了循环使用,下标不适合像顺序队列那样从-1的开始。
- 当队头和队尾相等时,队列是空的。
- 当(rear + 1) % maxSize = front时队列满了。加一是因为队尾和队头空了一个位置。对maxSize取模是为了循环。其实用取模实现循环在很多地方都有应用。
- 队列的元素的个数为:(rear+maxSize-front) % maxSize。
举个例子:队头为3,队尾为1:最大长度为5
(rear+maxSize-front) % maxSize=(1+5-3)%5=3 - 队头和队尾之间空了一个位置,因为,队尾的指针是指向下一个未被使用的位置。如果不空一个位置,那么就会发生队尾指针指向队头指针的情况。就和注意点2冲突了
type CircleQueue struct {
MaxSize int
Front int
Rear int
Element []int
}
// 初始化队列
func (cq *CircleQueue) initCircleQueue(maxSize int) {
cq.MaxSize = maxSize
cq.Element = make([]int, maxSize)
cq.Front = 0
cq.Rear = 0
}
// 判断队列是否为空
func (cq *CircleQueue) isEmpty() bool {
flag := false
if cq.Front == cq.Rear {
flag = true
}
return flag
}
// 判断队列是否已满
func (cq *CircleQueue) isFull() bool {
flag := false
if (cq.Rear+1)%cq.MaxSize == cq.Front {
flag = true
}
return flag
}
// 插入一个数据
func (cq *CircleQueue) add(data int) (err error) {
if cq.isFull() {
err = errors.New("the queue is full")
return
}
cq.Element[cq.Rear] = data
cq.Rear = (cq.Rear + 1) % cq.MaxSize
return
}
// 取出一个数据
func (cq *CircleQueue) poll() (data int, err error) {
if cq.isEmpty() {
err = errors.New("the queue is empty")
return
}
data = cq.Element[cq.Front]
cq.Front = (cq.Front + 1) % cq.MaxSize
return data, err
}
// 显示队列中所有的值
func (cq *CircleQueue) list() {
if cq.isEmpty() {
fmt.Println("the queue is empty")
return
}
// 因为队列的头不能改变,所以设置一个辅助遍历
tempFront := cq.Front
for i := 0; i < (cq.Rear+cq.MaxSize-cq.Front)%cq.MaxSize; i++ {
fmt.Printf("arr[%d]=%d\\n", tempFront, cq.Element[tempFront])
tempFront = (tempFront + 1) % cq.MaxSize
}
}
链式队列
链式队列和循环队列的比较:
- 时间上:都是O(1)
- 空间上:循环队列是事先申请好空间,使用期间不释放。而对于链式队列,每次申请和释放结点也都会花费一些时间,但是空间使用上更灵活一些。
- 在可以确定队列长度最大值的情况下优先使用循环队列,否则优先使用链式队列。
// 队列节点
type Node struct {
element int
next *Node
}
// 队列链表
type LinkedQueue struct {
Front *Node
Rear *Node
length int
}
// 初始化队列
func (lq *LinkedQueue) initLinkedQueue() {
lq.Front = nil
lq.Rear = nil
lq.length = 0
fmt.Printf("初始化成功!\\n")
}
// 判断队列是否为空
func (lq *LinkedQueue) isEmpty() bool {
flag := false
if lq.length == 0 {
flag = true
}
return flag
}
// 入队
func (lq *LinkedQueue) add(data int) {
// 构造新的节点
node := Node{element: data}
// 判断是否为第一次插入
if lq.isEmpty() {
lq.Front = &node
lq.Rear = &node
} else {
lq.Rear.next = &node
lq.Rear = &node
}
lq.length++
}
// 出队
func (lq *LinkedQueue) poll() (data int, err error) {
if lq.isEmpty() {
err = errors.New("the quenen is empty")
return
}
// 取出节点的数据
data = lq.Front.element
// 处理剩下最后一个节点的情况
if lq.length == 1 {
lq.Front = nil
lq.Rear = nil
lq.length--
return
}
lq.Front = lq.Front.next
lq.length--
return data, err
}
// 查看队列的所有元素
func (lq *LinkedQueue) list() {
if lq.isEmpty() {
fmt.Println("the queue is empty")
return
}
// 因为队列头不能变,所有用临时变量代替一下
tempFront := lq.Front
for i := 0; i < lq.length; i++ {
fmt.Println(tempFront.element)
tempFront = tempFront.next
}
}
最后
完整的代码在这里:https://github.com/bigzoro/go_algorithm/tree/main/queue
以上是关于Go数据结构与算法—队列的主要内容,如果未能解决你的问题,请参考以下文章