用go實現常用算法與數據結構——隊列(queue)


queue 簡介

隊列是一種非常常見的數據結構,日常生活中也能經常看到。一個典型的隊列如下圖(圖片來自 segmentfault):
queue
可以看出隊列和我們日常生活中排隊是基本一致的。都遵循 FIFO(First In First Out)的原則。

實現

隊列可以使用鏈表或者數組實現,使用鏈表的優點是擴容簡單,缺點是無法通過索引定位元素,使用數組則相反,擴容不容易但是可以通過索引定位元素。文章采用雙向鏈表實現。代碼放在github:

https://github.com/AceDarkknight/AlgorithmAndDataStructure/tree/master/queue

鏈表一般有下面這幾個基本操作,先定義一個接口,方便開發和測試:

type Queue interface {
    // 獲取當前鏈表長度。
	Length() int
	// 獲取當前鏈表容量。
	Capacity() int
	// 獲取當前鏈表頭結點。
	Front() *Node
	// 獲取當前鏈表尾結點。
	Rear() *Node
	// 入列。
	Enqueue(value interface{}) bool
	// 出列。
	Dequeue() interface{}
}

筆者的實現中,front 和 rear 節點不保存具體值,只是用來指示真正頭尾節點的位置。

鏈表實現的隊列

入列的實現如下:

// normalQueue.go
func (q *NormalQueue) Enqueue(value interface{}) bool {
	if q.length == q.capacity || value == nil {
		return false
	}

	node := &Node{
		value: value,
	}

	if q.length == 0 {
		q.front.next = node
	}

	node.previous = q.rear.previous
	node.next = q.rear
	q.rear.previous.next = node
	q.rear.previous = node
	q.length++

	return true
}

出列的實現:

// normalQueue.go
func (q *NormalQueue) Dequeue() interface{} {
	if q.length == 0 {
		return nil
	}

	result := q.front.next
	q.front.next = result.next
	result.next = nil
	result.previous = nil
	q.length--

	return result.value
}

可以看到,具體實現和鏈表基本一致,這種方法好處在於不需要考慮數組溢出的問題。但是有時候,我們可能會向 queue 插入相同的元素,我們當前的實現是無法判斷數據是否已經存在的,這時我們就需要實現一個無重復元素的 queue。

無重復元素的隊列。

我們只需要在原來的基礎上加一個 Map 存放我們的具體值就可以了。直接上代碼:

// uniqueQueue.go
func (q *UniqueQueue) Enqueue(value interface{}) bool {
	if q.length == q.capacity || value == nil {
		return false
	}

	node := &Node{
		value: value,
	}

	// Ignore uncomparable type.
	if kind := reflect.TypeOf(value).Kind(); kind == reflect.Map || kind == reflect.Slice || kind == reflect.Func {
		return false
	}

	if v, ok := q.nodeMap[value]; ok || v {
		return false
	}

	if q.length == 0 {
		q.front.next = node
	}

	node.previous = q.rear.previous
	node.next = q.rear
	q.rear.previous.next = node
	q.rear.previous = node

	q.nodeMap[value] = true

	q.length++

	return true
}

因為在 golang 中,map 的 key 必須是可以比較的,所以我們需要排除 Map、slice、function 這些不可比較的類型。剩下的實現和上面的就差不多了。再看出列操作:

// uniqueQueue.go
func (q *UniqueQueue) Dequeue() interface{} {
	if q.length == 0 {
		return nil
	}

	result := q.front.next

	delete(q.nodeMap, result.value)

	q.front.next = result.next
	result.next = nil
	result.previous = nil

	q.length--

	return result.value
}

上面兩個隊列都是基於鏈表實現的,下面介紹一下基於數組實現的循環隊列。

循環隊列

循環隊列通過復用數組元素來達到“循環”的效果。簡單來說就是如果數組前面有位置,就把元素放進去。具體原理可以看這里。入列代碼如下:

// cyclicQueue.go
func (q *CyclicQueue) Enqueue(value interface{}) bool {
	if q.length == q.capacity || value == nil {
		return false
	}

	node := &Node{
		value: value,
	}

	index := (q.rear + 1) % cap(q.nodes)
	q.nodes[index] = node
	q.rear = index
	q.length++

	if q.length == 1 {
		q.front = index
	}

	return true
}

出列操作也類似:

// cyclicQueue.go
func (q *CyclicQueue) Dequeue() interface{} {
	if q.length == 0 {
		return nil
	}

	result := q.nodes[q.front].value
	q.nodes[q.front] = nil
	index := (q.front + 1) % cap(q.nodes)
	q.front = index
	q.length--

	return result
}

Reference

https://www.geeksforgeeks.org/queue-set-1introduction-and-array-implementation/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM