使用Go語言實現跳錶程式


跳錶是一種動態資料結構,它提供高效的插入、搜尋和刪除操作。在本文中,我們將演示跳錶的功能,並探討使用Go語言實現跳錶的演算法概念。我們將為此演示編寫兩個不同的示例。在第一個示例中,我們將使用隨機化方法,在第二個示例中,我們將直接從隨機塔結構構建跳錶以實現更快的遍歷。

解釋

跳錶作為一種資料結構,維護一個已排序的元素列表,以便能夠快速搜尋而無需平衡樹的複雜性。這是透過建立多個具有不同跳躍距離的連結串列級別來實現的,這有助於更快地遍歷到所需的元素。

語法

func newNode(vlue, lvl int) *node

這段語法表示一個名為`newNode()`的方法,它接受一個值和一個整數來指定塔的高度,並返回一個設定了其值的節點以及一個指向不同級別後續節點的指標陣列,用於跳錶實現。

func (sl *skipList) randomLvl() int

這段語法表示一個名為`randomLvl()`的方法,它使用機率引數和最大級別來生成跳錶中新節點的隨機高度,以確定塔的高度。

演算法

  • 首先使用基數和最大級別初始化跳錶。

  • 為具有隨機級別的元素建立節點,最終形成塔狀結構。

  • 在水平方向上連線節點,並在垂直方向上跨級別連線。

  • 從左上角開始搜尋,然後向右或向下移動。

  • 對於插入操作,請更新節點連結,同時考慮塔的高度。

示例1

在這個示例中,我們將使用隨機化方法在Go語言中實現一個跳錶。`node`結構體儲存值和連結指標。`insert`函式透過遍歷級別來新增元素,`search`函式使用多級搜尋方法來查詢元素。`randomLvl()`方法生成塔的高度。我們使用陣列來表示每個節點中的下一個指標,用於建立直接儲存每個節點塔的級別及其與下一個陣列連線的塔結構。最後,在主函式中,初始化一個包含值的跳錶。最後,執行元素搜尋。

package main
 
import (
    "fmt"
    "math/rand"
)
const (
    maxLvl = 16
	p    	= 0.5
)
type node struct {
	vlue int
	nxt  []*node
}
type skipList struct{ head *node }
func newNode(vlue, lvl int) *node {
    return &node{vlue: vlue, nxt: make([]*node, lvl)}
}
func (sl *skipList) insert(vlue int) {
    lvl := sl.randomLvl()
    newNode := newNode(vlue, lvl)
	crrent := sl.head
    for i := lvl - 1; i >= 0; i-- {
        for crrent.nxt[i] != nil && crrent.nxt[i].vlue < vlue { crrent = crrent.nxt[i] }
        newNode.nxt[i], crrent.nxt[i] = crrent.nxt[i], newNode
    }
}
func (sl *skipList) search(vlue int) bool {
    crrent := sl.head
    for i := len(crrent.nxt) - 1; i >= 0; i-- {
        	for crrent.nxt[i] != nil && crrent.nxt[i].vlue < vlue { crrent = crrent.nxt[i] }
    }
	return crrent.nxt[0] != nil && crrent.nxt[0].vlue == vlue
}
func (sl *skipList) randomLvl() int {
	lvl := 1
	for rand.Float64() < p && lvl < maxLvl { lvl++ }
	return lvl
}
func main() {
    sl := &skipList{head: newNode(0, maxLvl)}
    vlues := []int{3, 6, 9, 2, 5, 8, 1, 4, 7}
	for _, v := range vlues { sl.insert(v) }
	fmt.Print("Skip List: ")
	crrent := sl.head.nxt[0]
	for crrent != nil { fmt.Printf("%d -> ", crrent.vlue); crrent = crrent.nxt[0] }
	fmt.Println("nil")
	fmt.Println("Search 5:", sl.search(5))
	fmt.Println("Search 10:", sl.search(10))
}

輸出

Skip List: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> nil
Search 5: true
Search 10: false

示例2

在這個示例中,我們將直接從隨機塔結構在Go語言中實現一個跳錶以實現更快的遍歷。透過建立塔狀連線來插入元素,並透過多級比較來執行類似的搜尋。生成隨機級別以保持平衡分佈。單獨的連結串列表示每個級別,連線由連結串列節點的幫助來管理。在主函式中,初始化一個跳錶,最後在其上執行元素搜尋。

package main
import (
    "fmt"
	"math/rand"
)
const (
	maxLvl = 16
	p    	= 0.5
)
type node struct {
	vlue int
	nxt  []*node
}
type skipList struct{ head *node }
func newNode(vlue, lvl int) *node {
    return &node{vlue: vlue, nxt: make([]*node, lvl)}
}
func (sl *skipList) insert(vlue int) {
	lvl, crrent := sl.randomLvl(), sl.head
	for i := lvl - 1; i >= 0; i-- {
     	for crrent.nxt[i] != nil && crrent.nxt[i].vlue < vlue {
            crrent = crrent.nxt[i]
        }
    newNode := newNode(vlue, i+1)
    newNode.nxt[i], crrent.nxt[i] = crrent.nxt[i], newNode
    }
}
func (sl *skipList) search(vlue int) bool {
    crrent := sl.head
    for i := len(crrent.nxt) - 1; i >= 0; i-- {
    	for crrent.nxt[i] != nil && crrent.nxt[i].vlue < vlue {
           crrent = crrent.nxt[i]
        }
    }
    return crrent.nxt[0] != nil && crrent.nxt[0].vlue == vlue
}
func (sl *skipList) randomLvl() int {
    lvl := 1
    for rand.Float64() < p && lvl < maxLvl {
        lvl++
    }
    return lvl
}
func main() {
    sl := &skipList{head: newNode(0, maxLvl)}
    vlues := []int{3, 6, 9, 2, 5, 8, 1, 4, 7}
    for _, v := range vlues {
    	sl.insert(v)
    }
    fmt.Println("Skip List:")
    for crrent := sl.head.nxt[0]; crrent != nil; crrent = crrent.nxt[0] {
        fmt.Printf("%d -> ", crrent.vlue)
    }
    fmt.Println("nil")
    fmt.Println("Search 5:", sl.search(5))
    fmt.Println("Search 10:", sl.search(10))
}

輸出

Skip List:
1 -> 2 -> 3 -> 5 -> 7 -> 8 -> 9 -> nil
Search 5: false
Search 10: false

現實生活中的應用

  • 遊戲開發:跳錶是保留遊戲中玩家分數排序組織的絕佳方法,當存在排行榜或最高分時。這使得更新和獲取最高分更容易。

  • 地理空間索引:跳錶已被發現是一種構建地理索引的潛在方法,以幫助有效儲存和檢索地理資料。使用這些索引允許有效地檢索附近的場所或區域,這對於基於位置的應用程式(例如地圖服務)非常重要。

結論

跳錶是一種多功能的資料結構,它融合了簡單性和效率,使其非常適合維護有序資料,並進行快速搜尋、插入和刪除操作。在本文中,我們探討了透過兩種不同的方法在Go語言中實現跳錶的概念:第一種方法比連結串列實現更節省記憶體,因為它避免了每個節點的單個指標的開銷。下一種方法與陣列方法相比提供了更好的靈活性和動態調整大小的功能,但另一方面,由於單個連結串列節點的開銷,它會消耗更多的記憶體。

更新於:2023年10月18日

228 次瀏覽

啟動您的職業生涯

透過完成課程獲得認證

開始學習
廣告
© . All rights reserved.