欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang標(biāo)準(zhǔn)庫container/list的用法圖文詳解

 更新時間:2024年01月02日 14:09:16   作者:畫個一樣的我  
提到單向鏈表,大家應(yīng)該是比較熟悉的了,這篇文章主要為大家詳細介紹了Golang標(biāo)準(zhǔn)庫container/list的用法相關(guān)知識,感興趣的小伙伴可以了解下

提到單向鏈表,大家應(yīng)該是比較熟悉的了。今天介紹的是 golang 官方庫提供的 雙向鏈表。

1、基礎(chǔ)介紹

單向鏈表中的每個節(jié)點包含數(shù)據(jù)和指向下一個節(jié)點的指針。其特點是每個節(jié)點只知道下一個節(jié)點的位置,使得數(shù)據(jù)只能單向遍歷。

示意圖如下:

雙向鏈表中的每個節(jié)點都包含指向前一個節(jié)點和后一個節(jié)點的指針。這使得在雙向鏈表中可以從前向后或從后向前遍歷。

示意圖如下:

結(jié)合上面的圖就很容易明白單、雙鏈表的定義。其中雙向鏈表可以從前向后,也可以從后向前遍歷,操作起來也更加方便。

接下來我們看看官方給的例子:

import (
	"container/list"
	"fmt"
)

func Example() {
	// Create a new list and put some numbers in it.
	l := list.New()
	e4 := l.PushBack(4)
	e1 := l.PushFront(1)
	l.InsertBefore(3, e4)
	l.InsertAfter(2, e1)

	// Iterate through list and print its contents.
	for e := l.Front(); e != nil; e = e.Next() {
		fmt.Println(e.Value)
	}

	// Output:
	// 1
	// 2
	// 3
	// 4
}

首先調(diào)用list.New()創(chuàng)建一個雙向鏈表,然后添加元素Element,最后從頭遍歷鏈表,打印每個元素的值。

從上可以看出,container/list提供了兩個結(jié)構(gòu) List、Element。

  • List
  • Element

平常自己學(xué)習(xí)算法實現(xiàn)的雙向鏈表也是這樣做的,只是元素一般命名為Node而已。

接下來,看看官方為 List 類型提供了哪些方法。

container/list

官方還是提供了豐富的API,接下來我們就一起看看源碼吧。

2、源碼分析

2.1、Element

// Element is an element of a linked list.
type Element struct {
	// Next and previous pointers in the doubly-linked list of elements.
	// To simplify the implementation, internally a list l is implemented
	// as a ring, such that &l.root is both the next element of the last
	// list element (l.Back()) and the previous element of the first list
	// element (l.Front()).
	next, prev *Element

	// The list to which this element belongs.
	list *List

	// The value stored with this element.
	Value any
}

Element 一共定義了四個字段,分別是指向前一個節(jié)點的 prev,指向下一個節(jié)點的 next,存儲值的 Value,以及 此元素屬于哪個list。

平常自己在定義雙向鏈表 Node 的結(jié)構(gòu)的時候,一般是不會有 list 這個元素的,為什么官方給的有這個元素呢?

說說自己的理解,很有可能有誤!

Element 的 list 字段是小寫的,那意味著外部使用者是無法獲取和定義此字段的,也就是說外部使用者無法通過 Element 來操作 鏈表。在通篇讀過源碼后,發(fā)現(xiàn) Element.list 是用于判斷插入、移動、刪除等操作的元素是否屬于此鏈表,所以我認為增加 list 字段的原因主要是安全性。

比如防止在多維鏈表操作的時候,錯誤的加入了不屬于此鏈表的節(jié)點,有了 list 字段后,就可以做判斷,防止這類情況產(chǎn)生。

Element 只有兩個方法,即 Next()、Prev(),源代碼如下:

// Next returns the next list element or nil.
func (e *Element) Next() *Element {
	if p := e.next; e.list != nil && p != &e.list.root {
		return p
	}
	return nil
}

// Prev returns the previous list element or nil.
func (e *Element) Prev() *Element {
	if p := e.prev; e.list != nil && p != &e.list.root {
		return p
	}
	return nil
}

看到這里,官方給的實現(xiàn)方式,并不是簡單的 e.prev、e.next,而是多了p != &e.list.root的判斷,為什么會有這個判斷呢?

因為container/list起始是一個環(huán)形鏈表,那么就需要有一個特殊的節(jié)點切斷這種環(huán)形關(guān)系,root就是用來做這個標(biāo)識的節(jié)點。

這樣做有什么好處呢?

root 字段是鏈表的根節(jié)點,它并不直接存儲數(shù)據(jù),而是一個空節(jié)點(Element 類型)。這個空節(jié)點被用作鏈表的哨兵節(jié)點(Sentinel Node)或者叫做標(biāo)志節(jié)點(Dummy Node)。

這個哨兵節(jié)點的作用是為了簡化鏈表的操作。通過將哨兵節(jié)點作為鏈表的根節(jié)點,在實際的鏈表操作中,就無需考慮頭節(jié)點為空的情況,即空鏈表和非空鏈表的操作邏輯變得更加統(tǒng)一和簡化。

  • 簡化邏輯: 哨兵節(jié)點的引入避免了對空鏈表的特殊處理。無論鏈表是否為空,頭節(jié)點(哨兵節(jié)點之后的第一個節(jié)點)始終存在,這樣在操作鏈表時就無需針對空鏈表做額外的判斷。
  • 邊界條件更清晰: 有了哨兵節(jié)點,鏈表的頭部和尾部都有了固定的節(jié)點作為標(biāo)志,使得鏈表操作時邊界條件更加清晰。
  • 提高代碼的一致性: 通過哨兵節(jié)點,鏈表的操作邏輯更加統(tǒng)一,減少了特殊情況下的代碼分支,提高了代碼的一致性和可讀性。

2.2、List

2.2.1 List 結(jié)構(gòu)

// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
type List struct {
	root Element // sentinel list element, only &root, root.prev, and root.next are used
	len  int     // current list length excluding (this) sentinel element
}

// Init initializes or clears list l.
func (l *List) Init() *List {
	l.root.next = &l.root
	l.root.prev = &l.root
	l.len = 0
	return l
}

// New returns an initialized list.
func New() *List { return new(List).Init() }

// Len returns the number of elements of list l.
// The complexity is O(1).
func (l *List) Len() int { return l.len }

因為container/list 是一個環(huán)形鏈表,所以只用提供一個節(jié)點就可以了。

注意:剛初始化時,即調(diào)用New生成的鏈表對象,此時的 root.next、root.prev 都是指向root 自己的 。當(dāng)使用 PushBack或者PushFront方法后,root.next 表示 Head Node,root.prev 表示 Tail Node。注意 List.len 的長度是不包含 root 節(jié)點的。

2.2.2、獲取頭尾節(jié)點

// Front returns the first element of list l or nil if the list is empty.
func (l *List) Front() *Element {
	if l.len == 0 {
		return nil
	}
	return l.root.next
}

// Back returns the last element of list l or nil if the list is empty.
func (l *List) Back() *Element {
	if l.len == 0 {
		return nil
	}
	return l.root.prev
}

有上面的介紹后,看這里的代碼就很簡單了。root.next 表示 Head Node,root.prev 表示 Tail Node。

2.2.3、鏈表基礎(chǔ)操作

在自己實現(xiàn)雙向鏈表時,主要難度在 插入、移動、刪除操作的實現(xiàn),不注意就會出現(xiàn)bug??纯垂俜绞侨绾巫龅?。

insert

將元素 e 插入到 元素 at 之后。

// insert inserts e after at, increments l.len, and returns e.
func (l *List) insert(e, at *Element) *Element {
	e.prev = at
	e.next = at.next
    
    // 此時的 e.prev 已經(jīng)是 at 節(jié)點
	e.prev.next = e
	e.next.prev = e
	e.list = l
	l.len++
	return e
}

// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
func (l *List) insertValue(v any, at *Element) *Element {
	return l.insert(&Element{Value: v}, at)
}

remove

// remove removes e from its list, decrements l.len
func (l *List) remove(e *Element) {
	e.prev.next = e.next
	e.next.prev = e.prev
	e.next = nil // avoid memory leaks
	e.prev = nil // avoid memory leaks
	e.list = nil
	l.len--
}

move

// move moves e to next to at.
func (l *List) move(e, at *Element) {
	if e == at {
		return
	}
	// 移除到 e 在原來鏈表中的關(guān)系
	e.prev.next = e.next
	e.next.prev = e.prev
	
	// 這里和 insert 操作是一樣的
	e.prev = at
	e.next = at.next
	e.prev.next = e
	e.next.prev = e
}

因為這里是移動節(jié)點位置,不是新增元素,所以鏈表長度不用調(diào)整。

2.2.4、常用API

下面這些對外提供的 API 就是基于上面的基礎(chǔ)操作實現(xiàn)的,自行閱讀即可。

// Remove removes e from l if e is an element of list l.
// It returns the element value e.Value.
// The element must not be nil.
func (l *List) Remove(e *Element) any {
	if e.list == l {
		// if e.list == l, l must have been initialized when e was inserted
		// in l or l == nil (e is a zero Element) and l.remove will crash
		l.remove(e)
	}
	return e.Value
}

// PushFront inserts a new element e with value v at the front of list l and returns e.
func (l *List) PushFront(v any) *Element {
	l.lazyInit()
	return l.insertValue(v, &l.root)
}

// PushBack inserts a new element e with value v at the back of list l and returns e.
func (l *List) PushBack(v any) *Element {
	l.lazyInit()
	return l.insertValue(v, l.root.prev)
}

// InsertBefore inserts a new element e with value v immediately before mark and returns e.
// If mark is not an element of l, the list is not modified.
// The mark must not be nil.
func (l *List) InsertBefore(v any, mark *Element) *Element {
	if mark.list != l {
		return nil
	}
	// see comment in List.Remove about initialization of l
	return l.insertValue(v, mark.prev)
}

// InsertAfter inserts a new element e with value v immediately after mark and returns e.
// If mark is not an element of l, the list is not modified.
// The mark must not be nil.
func (l *List) InsertAfter(v any, mark *Element) *Element {
	if mark.list != l {
		return nil
	}
	// see comment in List.Remove about initialization of l
	return l.insertValue(v, mark)
}

// MoveToFront moves element e to the front of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
func (l *List) MoveToFront(e *Element) {
	if e.list != l || l.root.next == e {
		return
	}
	// see comment in List.Remove about initialization of l
	l.move(e, &l.root)
}

// MoveToBack moves element e to the back of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
func (l *List) MoveToBack(e *Element) {
	if e.list != l || l.root.prev == e {
		return
	}
	// see comment in List.Remove about initialization of l
	l.move(e, l.root.prev)
}

// MoveBefore moves element e to its new position before mark.
// If e or mark is not an element of l, or e == mark, the list is not modified.
// The element and mark must not be nil.
func (l *List) MoveBefore(e, mark *Element) {
	if e.list != l || e == mark || mark.list != l {
		return
	}
	l.move(e, mark.prev)
}

// MoveAfter moves element e to its new position after mark.
// If e or mark is not an element of l, or e == mark, the list is not modified.
// The element and mark must not be nil.
func (l *List) MoveAfter(e, mark *Element) {
	if e.list != l || e == mark || mark.list != l {
		return
	}
	l.move(e, mark)
}

// PushBackList inserts a copy of another list at the back of list l.
// The lists l and other may be the same. They must not be nil.
func (l *List) PushBackList(other *List) {
	l.lazyInit()
	for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() {
		l.insertValue(e.Value, l.root.prev)
	}
}

// PushFrontList inserts a copy of another list at the front of list l.
// The lists l and other may be the same. They must not be nil.
func (l *List) PushFrontList(other *List) {
	l.lazyInit()
	for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() {
		l.insertValue(e.Value, &l.root)
	}
}

3、案例

有了上面的基礎(chǔ)后,我們再來實戰(zhàn)下。

需求:實現(xiàn)一個二維鏈表,要求第一維以價格從低到高排序,第二維以時間從小到大排序。

package main

import (
	"container/list"
	"fmt"
	"sort"
	"strings"
	"time"
)


type Order struct {
	Price       float64
	CreatedTime time.Time
}

// TwoDList 二維鏈表,要求第一維以價格從低到高排序,第二維以時間從小到大排序。
type TwoDList struct {
	// 索引相同,即表示價格相同,同一索引的鏈表節(jié)點,越靠后時間越大
	// 索引越大,價格越高
	Rows []*list.List
}

func NewTwoDList() *TwoDList {
	return &TwoDList{
		Rows: make([]*list.List, 0),
	}
}

func (tdl *TwoDList) AddNode(price float64, createdTime time.Time) {

	order := &Order{Price: price, CreatedTime: createdTime}
	// 1、
	index := sort.Search(len(tdl.Rows), func(i int) bool {
		return tdl.Rows[i].Front().Value.(*Order).Price >= order.Price
	})
	if index == len(tdl.Rows) {
		// 此價格不存在 tdl 中, 新增
		newList := list.New()
		newList.PushFront(order)

		tdl.Rows = append(tdl.Rows, newList)
		return
	}

	// 判斷 index 處的價格是否和 order.Price 相等,
	// 相等, 則往鏈表添加
	// 不相等, 則需要先將 index 之后的往后移一位
	if order.Price != tdl.Rows[index].Front().Value.(*Order).Price {
		newList := list.New()
		newList.PushFront(order)

		// 插入元素 newList
		tdl.Rows = append(tdl.Rows[:index], append([]*list.List{newList}, tdl.Rows[index:]...)...)
		return
	}

	// 時間從小到大排
	curRow := tdl.Rows[index]
	insertPosition := curRow.Front()

	for insertPosition != nil && order.CreatedTime.After(insertPosition.Value.(*Order).CreatedTime) {
		insertPosition = insertPosition.Next()
	}

	if insertPosition == nil {
		curRow.PushBack(order)
	} else {
		curRow.InsertBefore(order, insertPosition)
	}
}

func (tdl *TwoDList) Print() {
	for i, row := range tdl.Rows {
		fmt.Printf("index: %d\n", i)
		for node := row.Front(); node != nil; node = node.Next() {
			order := node.Value.(*Order)
			fmt.Printf("order price: %f, time: %v \n", order.Price, order.CreatedTime)
		}
		fmt.Println(strings.Repeat("-", 20))
	}
}

func main() {
	// 創(chuàng)建一個新的二維鏈表
	myTwoDList := NewTwoDList()

	// 向二維鏈表添加節(jié)點
	myTwoDList.AddNode(100, time.Now())
	myTwoDList.AddNode(75, time.Now().Add(time.Hour))
	myTwoDList.AddNode(75, time.Now().Add(time.Hour))
	myTwoDList.AddNode(150, time.Now().Add(2*time.Hour))
	myTwoDList.AddNode(75, time.Now().Add(3*time.Hour))
	myTwoDList.AddNode(200, time.Now().Add(4*time.Hour))

	// 打印二維鏈表
	myTwoDList.Print()
}

運行結(jié)果:

index: 0
order price: 75.000000, time: 2024-01-02 12:27:34.2398306 +0800 CST m=+3600.004429301  
order price: 75.000000, time: 2024-01-02 12:27:34.2398306 +0800 CST m=+3600.004429301  
order price: 75.000000, time: 2024-01-02 14:27:34.2398306 +0800 CST m=+10800.004429301 
--------------------
index: 1
order price: 100.000000, time: 2024-01-02 11:27:34.2398306 +0800 CST m=+0.004429301    
--------------------
index: 2
order price: 150.000000, time: 2024-01-02 13:27:34.2398306 +0800 CST m=+7200.004429301 
--------------------
index: 3
order price: 200.000000, time: 2024-01-02 15:27:34.2398306 +0800 CST m=+14400.004429301
--------------------

到此這篇關(guān)于Golang標(biāo)準(zhǔn)庫container/list的用法圖文詳解的文章就介紹到這了,更多相關(guān)Go container/list內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • golang flag介紹和使用示例

    golang flag介紹和使用示例

    本文主要介紹了Go語言中flag包的使用方法,詳細闡述了基本概念及常用函數(shù),并通過示例代碼進行了具體演示,總結(jié)中指出,flag包提供了一種方便的方式來處理命令行參數(shù),可定義不同類型的標(biāo)志,并在解析后使用這些參數(shù)
    2024-10-10
  • 基于原生Go語言開發(fā)一個博客系統(tǒng)

    基于原生Go語言開發(fā)一個博客系統(tǒng)

    這篇文章主要為大家詳細介紹了如何基于原生Go語言開發(fā)一個簡單的博客系統(tǒng),文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-02-02
  • Go編譯原理之函數(shù)內(nèi)聯(lián)

    Go編譯原理之函數(shù)內(nèi)聯(lián)

    這篇文章主要為大家介紹了Go編譯原理之函數(shù)內(nèi)聯(lián)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • 一文了解Go語言的并發(fā)特性

    一文了解Go語言的并發(fā)特性

    本文主要介紹了一文了解Go語言的并發(fā)特性,通過輕量級線程、通道及選擇語句,使得并發(fā)編程變得既簡單又高效,下面就來具體了解一下如何使用,感興趣的可以了解一下
    2024-02-02
  • Go語言map用法實例分析

    Go語言map用法實例分析

    這篇文章主要介紹了Go語言map用法,實例分析了map的功能及使用技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02
  • 詳解如何在Go中循環(huán)中使用Defer關(guān)鍵字示例詳解

    詳解如何在Go中循環(huán)中使用Defer關(guān)鍵字示例詳解

    這篇文章主要為大家介紹了詳解如何在Go中循環(huán)中使用Defer關(guān)鍵字示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • Go實現(xiàn)凱撒密碼加密解密

    Go實現(xiàn)凱撒密碼加密解密

    這篇文章主要為大家介紹了Go實現(xiàn)凱撒密碼加密解密示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • Go語言中關(guān)閉帶緩沖區(qū)的頻道實例分析

    Go語言中關(guān)閉帶緩沖區(qū)的頻道實例分析

    這篇文章主要介紹了Go語言中關(guān)閉帶緩沖區(qū)的頻道,實例分析了帶緩沖區(qū)頻道的原理與用法,以及關(guān)閉帶緩沖區(qū)頻道的技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02
  • golang協(xié)程池設(shè)計詳解

    golang協(xié)程池設(shè)計詳解

    這篇文章主要介紹了golang協(xié)程池設(shè)計詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • go常用指令之go?mod詳解

    go常用指令之go?mod詳解

    當(dāng)go命令運行時,它查找當(dāng)前目錄然后查找相繼的父目錄來找出 go.mod,下面這篇文章主要給大家介紹了關(guān)于go常用指令之go?mod的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-08-08

最新評論