golang算法練習:單鏈表/雙鏈表/環形鏈表

需求

鏈表,常見且非常靈活的數據模型,可定製性強,可根據需求調整滿足不同的使用需求,如FIFO\LIFO,快速查找等,這裏分別列舉基礎的單向鏈表和雙向鏈表增刪改查操作
備註:需求和運行輸出結果均已在代碼中註釋

單向鏈表

代碼

package main

import (
	"errors"
	"fmt"
)

type Node struct {
	id   int
	name string
	next *Node
}

type SingleLink struct {
	head *Node
}

func (singleLink *SingleLink) orderInsert(node *Node) {
	// 按Node.id從小到大的順序,插入鏈表中
	curNode := singleLink.head
	if curNode.id == 0 {
		// 第一次插入元素的空鏈表
		singleLink.head = node
	} else {
		// 如果node.id比隊首id小,則隊首讓位後移,要保證隊首最小。因爲取值比較的時候, 是取curNode.next節點與node進行比較,如果滿足大小順序,
		// 則將node插入curNode和curNode.next之間
		if curNode.id > node.id {
			node.next = curNode
			singleLink.head = node
		} else {
			// 如果node.id不比隊首id小,則往後取一位,用後一位與node比較,匹配則插入當前位與後一位中間
			for {
				if curNode.next == nil {
					// 循環到最後一位都沒有插入成功,說明node.id比現有鏈表中的所有node的id都大,則node成爲新的隊尾
					curNode.next = node
					break
				}
				if curNode.next.id > node.id {
					node.next = curNode.next
					curNode.next = node
					break
				}
				curNode = curNode.next
			}
		}
	}
}

func (singleLink *SingleLink) pop(id int) (node *Node, err error) {
	// 彈出鏈表中指定id的node
	curNode := singleLink.head
	if curNode.id == id {
		// 要取的是隊首
		singleLink.head = singleLink.head.next
		return curNode, nil
	}
	flag := false // 是否查找到的標誌
	for {
		if curNode.next == nil {
			break
		}
		if curNode.next.id == id {
			flag = true
			selectNode := curNode.next
			curNode.next = curNode.next.next
			return selectNode, nil
		}
		curNode = curNode.next
	}
	if !flag {
		// 如果循環結束還沒有查找到,輸出信息
		return nil, errors.New("node not found")
	}
	return
}

func (singleLink *SingleLink) getLength() (number int) {
	curNode := singleLink.head
	if singleLink.head.id == 0 || singleLink.head == nil {
		// 空鏈表
		return
	} else {
		for {
			number += 1
			curNode = curNode.next
			if curNode == nil {
				break
			}
		}
	}
	fmt.Println("current link length:", number)
	return number
}

func (singleLink *SingleLink) list() {
	if singleLink.head == nil || singleLink.head.id == 0 {
		fmt.Println("link is empty")
	} else {
		curNode := singleLink.head
		for {
			if curNode == nil {
				break
			}
			fmt.Print(*curNode, "==>")
			curNode = curNode.next
		}
	}
	fmt.Println()

}

func main() {
	// 約定空鏈表的header初始id爲0,後面加入的實例id必須大於0
	var originalHead = Node{
		id: 0,
	}
	var s = SingleLink{
		head: &originalHead,
	}
	var node1 = Node{
		id:   1,
		name: "001",
	}
	var node2 = Node{
		id:   2,
		name: "002",
	}
	var node3 = Node{
		id:   3,
		name: "003",
	}
	var node4 = Node{
		id:   4,
		name: "004",
	}
	var node5 = Node{
		id:   5,
		name: "005",
	}

	s.orderInsert(&node4)
	s.list()
	s.orderInsert(&node2)
	s.list()
	s.orderInsert(&node3)
	s.list()
	s.orderInsert(&node5)
	s.list()
	s.orderInsert(&node1)
	s.list()
	s.getLength()
	popNode, err := s.pop(3)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("pop node successfully:", *popNode)
	s.list()

	/*
		output:
			{4 004 <nil>}==>
			{2 002 0xc00000a0e0}==>{4 004 <nil>}==>
			{2 002 0xc00000a0c0}==>{3 003 0xc00000a0e0}==>{4 004 <nil>}==>
			{2 002 0xc00000a0c0}==>{3 003 0xc00000a0e0}==>{4 004 0xc00000a100}==>{5 005 <nil>}==>
			{1 001 0xc00000a0a0}==>{2 002 0xc00000a0c0}==>{3 003 0xc00000a0e0}==>{4 004 0xc00000a100}==>{5 005 <nil>}==>
			current link length: 5
			pop node successfully: {3 003 0xc00000a0e0}
			{1 001 0xc00000a0a0}==>{2 002 0xc00000a0e0}==>{4 004 0xc00000a100}==>{5 005 <nil>}==>
	*/
}

問題
單向鏈表的操作必須從首部開始,那麼可不可以靈活一些,比如從尾部開始呢?當然可以,改造成雙向鏈表即可滿足

雙向鏈表

代碼:
爲了以示區別,增刪改查全部改爲從尾部開始循環

package main

import (
	"errors"
	"fmt"
)

type Node struct {
	id   int
	name string
	next *Node
	prev *Node
}

type CircleLink struct {
	head *Node
	tail *Node
}

func (circleLink *CircleLink) orderInsert(node *Node) {
	// 按Node.id從小到大的順序,插入鏈表中
	curNode := circleLink.head
	if curNode.id == 0 {
		// 第一次插入元素的空鏈表
		circleLink.head = node
		circleLink.tail = node
	} else {
		// 如果node.id比隊首id小,則隊首讓位後移,要保證隊首最小。因爲取值比較的時候, 是取curNode.next節點與node進行比較,如果滿足大小順序,
		// 則將node插入curNode和curNode.next之間
		if curNode.id > node.id {
			node.next = curNode
			curNode.prev = node
			circleLink.head = node
		} else {
			// 如果node.id不比隊首id小,則往後取一位,用後一位與node比較,匹配則插入當前位與後一位中間
			for {
				if curNode.next == nil {
					// 循環到最後一位都沒有插入成功,說明node.id比現有鏈表中的所有node的id都大,則node成爲新的隊尾
					curNode.next = node
					node.prev = curNode
					circleLink.tail = node
					break
				}
				if curNode.next.id > node.id {
					node.next = curNode.next
					node.prev = curNode
					curNode.next.prev = node
					curNode.next = node
					break
				}
				curNode = curNode.next
			}
		}
	}
}

func (circleLink *CircleLink) pop(id int) (node *Node, err error) {
	// 彈出鏈表中指定id的node.這次從鏈表尾部往首部循環
	curNode := circleLink.tail
	if curNode.id == id {
		// 要取的是隊尾
		circleLink.tail = circleLink.tail.prev
		return curNode, nil
	}
	flag := false // 是否查找到的標誌
	for {
		if curNode.prev == nil {
			// 已經從隊尾達到隊首,全部都沒命中
			break
		}
		if curNode.id == id {
			flag = true
			selectNode := curNode
			if curNode == circleLink.head {
				// 如果命中的節點正好是隊首
				if circleLink.head.next != nil {
					// 如果隊首後面還有節點,則第二個節點置爲新的首節點,且第二個節點的prev指向置爲nil,這樣原head沒有被引用,內存會釋放出來
					circleLink.head.next.prev = nil
					circleLink.head = circleLink.head.next
				} else {
					// 如果隊首之後沒有節點了,那麼直接首位都置空
					circleLink.head = nil
					circleLink.tail = nil
				}
			} else {
				// 命中的節點在隊中間,則取出命中的節點,並維護好其前後指向關係
				curNode.prev.next = curNode.next
				curNode.next.prev = curNode.prev
			}
			return selectNode, nil
		}
		curNode = curNode.prev
	}
	if !flag {
		// 如果循環結束還沒有查找到,輸出信息
		return nil, errors.New("node not found")
	}
	return
}

func (circleLink *CircleLink) getLength() (number int) {
	curNode := circleLink.tail
	if circleLink.tail.id == 0 || circleLink.tail == nil {
		// 空鏈表
		return
	} else {
		for {
			number += 1
			curNode = curNode.prev
			if curNode == nil {
				break
			}
		}
	}
	fmt.Println("current link length:", number)
	return number
}

func (circleLink *CircleLink) list() {
	if circleLink.tail == nil || circleLink.tail.id == 0 {
		fmt.Println("link is empty")
	} else {
		curNode := circleLink.tail
		var nodes []*Node
		for {
			if curNode == nil {
				break
			}
			//fmt.Print("<==", *curNode)
			// 從尾部到首部的循環,爲了展示鏈接效果,將節點先加入一個slice中,稍後從slice的後面開始循環展示數據.此時slice是從尾部到首部排序的
			nodes = append(nodes, curNode)
			curNode = curNode.prev
		}
		for i := 0; i < len(nodes); i++ {
			index := len(nodes) - i - 1 // 顛倒一下順序,現在slice中的順序和鏈表順序恰好是相反的,爲了打印的效果,讓前面的節點先輸出
			fmt.Print("<==>", nodes[index])
		}
	}

	fmt.Println()

}

func main() {
	// 約定空鏈表的header初始id爲0,後面加入的實例id必須大於0
	var originalHead = Node{
		id: 0,
	}
	var s = CircleLink{
		head: &originalHead,
	}
	var node1 = Node{
		id:   1,
		name: "001",
	}
	var node2 = Node{
		id:   2,
		name: "002",
	}
	var node3 = Node{
		id:   3,
		name: "003",
	}
	var node4 = Node{
		id:   4,
		name: "004",
	}
	var node5 = Node{
		id:   5,
		name: "005",
	}

	s.orderInsert(&node4)
	s.list()
	s.orderInsert(&node2)
	s.list()
	s.orderInsert(&node3)
	s.list()
	s.orderInsert(&node5)
	s.list()
	s.orderInsert(&node1)
	s.list()
	s.getLength()
	popNode, err := s.pop(3)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("pop node successfully:", *popNode)
	s.list()

	/*
		output:
			<==&{4 004 <nil> <nil>}
			<==&{2 002 0xc0000761e0 <nil>}<==&{4 004 <nil> 0xc000076180}
			<==&{2 002 0xc0000761b0 <nil>}<==&{3 003 0xc0000761e0 0xc000076180}<==&{4 004 <nil> 0xc0000761b0}
			<==&{2 002 0xc0000761b0 <nil>}<==&{3 003 0xc0000761e0 0xc000076180}<==&{4 004 0xc000076210 0xc0000761b0}<==&{5 005 <nil> 0xc0000761e0}
			<==&{1 001 0xc000076180 <nil>}<==&{2 002 0xc0000761b0 0xc000076150}<==&{3 003 0xc0000761e0 0xc000076180}<==&{4 004 0xc000076210 0xc0000761b0}<==&{5 005 <nil> 0xc0000761e0}
			current link length: 5
			pop node successfully: {3 003 0xc0000761e0 0xc000076180}
			<==&{1 001 0xc000076180 <nil>}<==&{2 002 0xc0000761e0 0xc000076150}<==&{4 004 0xc000076210 0xc000076180}<==&{5 005 <nil> 0xc0000761e0}
	*/
}

問題
從尾部開始查詢的操作複雜度依舊與首部開始一樣,爲O(n),有沒有辦法降低複雜度?當然可以,按照自定義鏈節點串聯規則,完全可以使用二分等其他查找的方法降低複雜度,這個後面再補充

環形鏈表

直接以約瑟夫問題,又稱丟手帕遊戲舉例,此場景非常適用於使用環形鏈表模型,遊戲規則見代碼頂部註釋
代碼:

package main

import (
	"fmt"
	"math/rand"
)

/*
約瑟夫問題,又叫丟手帕遊戲,若干人圍成一圈,從頭開始,每次遞進若干位隨機數,然後出列一人,接着再從出列者的下一位開始繼續循環,
直到圈內只剩最後一人,遊戲結束
*/

type Child struct {
	Id   int
	next *Child
}

type Ring struct {
	Head *Child
	Tail *Child
}

func (ring *Ring) list() {
	curChild := ring.Head
	for {
		fmt.Printf("child %d ==>\t", curChild.Id)
		if curChild.next == ring.Head {
			break
		}
		curChild = curChild.next
	}
	fmt.Println()
}

func (ring *Ring) play() {
	// 開始轉圈,第一次是從頭開始
	startIndex := 1
	n := 0
	startChild := ring.Head
	for {
		if startChild.next == startChild {
			fmt.Printf("遊戲結束,圈圈中的最後一位child是%d號\n", startChild.Id)
			break
		}
		n += 1
		randInt := rand.Intn(10)
		curChild := startChild
		for i := 0; i < randInt-1; i++ {
			// 在出列child的前一位停下,因爲是鏈表關係,所以需要維護好出列child的前一位的指向關係
			curChild = curChild.next
		}
		chooseChild := curChild.next
		if chooseChild == ring.Head {
			ring.Head = chooseChild.next
		}
		curChild.next = chooseChild.next // chooseChild的指針取消,chooseChild會被回收,出列
		fmt.Printf("第%d輪,從第%d號開始,前進%d位,出列的是%d號child,game continue!\n", n, startIndex, randInt, chooseChild.Id)
		startIndex = (randInt + startIndex + 1) % 20
		startChild = chooseChild.next // 下一輪,從chooseChild.next節點開始
	}

}

func ringInit(number int) (ring Ring) {
	// 構建一個指定成員數量的環形鏈表
	for i := 1; i <= number; i++ {
		var child = Child{
			Id: i,
		}
		fmt.Println(child)
		if i == 1 {
			// 插入第一個節點
			ring.Head = &child
			ring.Tail = &child
			ring.Tail.next = ring.Head // 尾節點下一個指針指向首節點
		} else {
			// 後面的節點,陸續成爲新的尾結點
			child.next = ring.Head
			ring.Tail.next = &child
			ring.Tail = &child
		}
	}
	return
}

func main() {
	ring := ringInit(20)
	fmt.Println(*ring.Head, *ring.Tail)
	ring.list()
	ring.play()
}
/*
	output:
		第1輪,從第1號開始,前進1位,出列的是2號child,game continue!
		第2輪,從第3號開始,前進7位,出列的是10號child,game continue!
		第3輪,從第11號開始,前進7位,出列的是18號child,game continue!
		第4輪,從第19號開始,前進9位,出列的是9號child,game continue!
		第5輪,從第9號開始,前進1位,出列的是12號child,game continue!
		第6輪,從第11號開始,前進8位,出列的是3號child,game continue!
		第7輪,從第0號開始,前進5位,出列的是11號child,game continue!
		第8輪,從第6號開始,前進0位,出列的是14號child,game continue!
		第9輪,從第7號開始,前進6位,出列的是4號child,game continue!
		第10輪,從第14號開始,前進0位,出列的是6號child,game continue!
		第11輪,從第15號開始,前進4位,出列的是16號child,game continue!
		第12輪,從第0號開始,前進1位,出列的是19號child,game continue!
		第13輪,從第2號開始,前進2位,出列的是5號child,game continue!
		第14輪,從第5號開始,前進9位,出列的是13號child,game continue!
		第15輪,從第15號開始,前進8位,出列的是20號child,game continue!
		第16輪,從第4號開始,前進4位,出列的是17號child,game continue!
		第17輪,從第9號開始,前進1位,出列的是7號child,game continue!
		第18輪,從第11號開始,前進5位,出列的是1號child,game continue!
		第19輪,從第17號開始,前進7位,出列的是15號child,game continue!
		遊戲結束,圈圈中的最後一位child是8號
*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章