introduce
A queue is a special linear table. A queue allows data to be inserted at one end and retrieved at the other end, and the data in the queue follows the First In First Our (FIFO) rule. One end of the retrieved data is called the head of the queue, and the other end of the inserted data is called the end of the queue.
Queues can be divided into: sequential queue, circular queue, chain queue.
Sequential Queue
type Queue struct { MaxSize int Front int Rear int Element []int } // Initialize Queue func (q *Queue) initQueue(maxSize int) { q.MaxSize = maxSize q.Element = make([]int, maxSize) q.Front = -1 q.Rear = -1 } // Determine if the queue is empty func (q *Queue) isEmpty() bool { flag := false if q.Front == q.Rear { flag = true } return flag } // Add an element to the queue func (q *Queue) add(data int) (err error) { // Full Judgement if q.Rear == q.MaxSize-1 { err = errors.New("The queue is full and cannot be added!") return err } q.Rear++ q.Element[q.Rear] = data return } // Get an element of the queue 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 } // View all elements of the queue 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. Initialize Queue") fmt.Println("1. Add data to queue") fmt.Println("2. Get data from a queue") fmt.Println("3. Display queue data") fmt.Println("4. Exit the program") fmt.Println("Please select (0)-4): ") fmt.Scanf("%d\n", &key) switch key { case 0: fmt.Println("Please enter the capacity of the initialization queue") fmt.Scanf("%d\n", &size) queue.initQueue(size) case 1: fmt.Println("Enter the data you want to add:") 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("Data taken from the queue is: %d\n", data) case 3: queue.list() case 4: os.Exit(0) } } }
Sequential queues can cause false overflow because their storage units do not have a mechanism for reuse. Therefore, the space utilization of sequential queues is not too high. The solution is to design sequential queues as a circular structure.
Circular Queue
A circular queue logically connects the end and end of the queue. This allows the location of the data removed to continue to be used in the next cycle, improving the space utilization of the queue.
The following points need attention:
- Both the queue header and queue tail subscripts start at 0. For recycling purposes, subscripts are not suitable to start at -1 as sequential queues do.
- When the head and tail of the queue are equal, the queue is empty.
- When (rear + 1)% maxSize = front, the queue is full. Plus one is because there is an empty position at the end and the head of the queue. Modifying maxSize is for looping. Actual mocking is used in many places to achieve looping.
- The number of elements in the queue is: (rear+maxSize-front)% maxSize.
For example, the head of the team is 3 and the end of the team is 1: the maximum length is 5
(rear+maxSize-front) % maxSize=(1+5-3)%5=3 - A position is empty between the queue head and the queue tail because the pointer at the queue tail points to the next unused position. If you do not empty a position, then the situation where the queue tail pointer points to the queue head pointer occurs. This conflicts with Point 2.
type CircleQueue struct { MaxSize int Front int Rear int Element []int } // Initialize Queue func (cq *CircleQueue) initCircleQueue(maxSize int) { cq.MaxSize = maxSize cq.Element = make([]int, maxSize) cq.Front = 0 cq.Rear = 0 } // Determine if the queue is empty func (cq *CircleQueue) isEmpty() bool { flag := false if cq.Front == cq.Rear { flag = true } return flag } // Determine if the queue is full func (cq *CircleQueue) isFull() bool { flag := false if (cq.Rear+1)%cq.MaxSize == cq.Front { flag = true } return flag } // Insert a Data 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 } // Take out a data 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 } // Show all values in the queue func (cq *CircleQueue) list() { if cq.isEmpty() { fmt.Println("the queue is empty") return } // Set an auxiliary traversal because the header of the queue cannot be changed 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 } }
Chained Queue
Comparison of chained and circular queues:
- Time: All O(1)
- Spatially: A circular queue requests space in advance and does not release during use. For a chain queue, it takes some time for each request and release node, but it is more flexible in space use.
- Cyclic queues are preferred when the maximum queue length can be determined, or chained queues are preferred.
// Queue Node type Node struct { element int next *Node } // Queue Chain List type LinkedQueue struct { Front *Node Rear *Node length int } // Initialize Queue func (lq *LinkedQueue) initLinkedQueue() { lq.Front = nil lq.Rear = nil lq.length = 0 fmt.Printf("Initialization succeeded!\n") } // Determine if the queue is empty func (lq *LinkedQueue) isEmpty() bool { flag := false if lq.length == 0 { flag = true } return flag } // Entry func (lq *LinkedQueue) add(data int) { // Construct a new node node := Node{element: data} // Determine if this is the first insert if lq.isEmpty() { lq.Front = &node lq.Rear = &node } else { lq.Rear.next = &node lq.Rear = &node } lq.length++ } // Queue func (lq *LinkedQueue) poll() (data int, err error) { if lq.isEmpty() { err = errors.New("the quenen is empty") return } // Remove data from node data = lq.Front.element // Handle the case where the last node is left if lq.length == 1 { lq.Front = nil lq.Rear = nil lq.length-- return } lq.Front = lq.Front.next lq.length-- return data, err } // View all elements of the queue func (lq *LinkedQueue) list() { if lq.isEmpty() { fmt.Println("the queue is empty") return } // Because the queue head cannot be changed, all temporary variables are used instead tempFront := lq.Front for i := 0; i < lq.length; i++ { fmt.Println(tempFront.element) tempFront = tempFront.next } }
Last
The complete code is here: https://github.com/bigzoro/go_algorithm/tree/main/queue