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)
		
	


顺序队列会产生假溢出的现象,因为顺序队列的存储单元没有重复使用的机制。所以顺序队列的空间利用率也不是太高。解决的办法就是将顺序队列设计成循环结构。

循环队列

循环队列在逻辑上将队列的首尾进行相连。这样,被取出数据的位置在下一轮循环中可以继续使用,提高了队列的空间利用率。

以下几点需要注意:

  1. 队头和队尾下标都是从0开始的,为了循环使用,下标不适合像顺序队列那样从-1的开始。
  2. 当队头和队尾相等时,队列是空的。
  3. 当(rear + 1) % maxSize = front时队列满了。加一是因为队尾和队头空了一个位置。对maxSize取模是为了循环。其实用取模实现循环在很多地方都有应用。
  4. 队列的元素的个数为:(rear+maxSize-front) % maxSize。

    举个例子:队头为3,队尾为1:最大长度为5
    (rear+maxSize-front) % maxSize=(1+5-3)%5=3

  5. 队头和队尾之间空了一个位置,因为,队尾的指针是指向下一个未被使用的位置。如果不空一个位置,那么就会发生队尾指针指向队头指针的情况。就和注意点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 := Nodeelement: 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数据结构与算法—队列的主要内容,如果未能解决你的问题,请参考以下文章

数据结构与算法——栈和队列

Go语言核心36讲

算法导论第10章,基本数据结构

手撸golang 基本数据结构与算法 图的搜索 深度优先/广度优先

go语言数据结构 环形队列

数据结构与算法入门---基本概念