使用golang簡單實(shí)現(xiàn)跳躍表SkipList

? ? 有關(guān)SkipList的定義拯辙,請參考跳躍表(可能需要科學(xué)上網(wǎng))郭变。我們知道,有序鏈表涯保,無論是單向還是雙向诉濒,增刪改查時間復(fù)雜度都是O(n)。跳躍表的存在就是為了解決這個問題夕春,解決思路為空間換時間未荒,不過空間復(fù)雜度沒有提升,仍然為O(n)(理論上使用的空間變?yōu)樵瓉淼?-2倍及志,數(shù)學(xué)期望為n+log(n)倍)片排,但是增刪改查的時間復(fù)雜度降低為O(log(n)),其性能可以媲美二叉樹家族(AVL樹速侈,紅黑樹)率寡,但在并發(fā)情況下,二叉樹在增刪操作后進(jìn)行重新平衡需要加鎖的節(jié)點(diǎn)要比跳躍表多倚搬,所以整體性能表現(xiàn)上冶共,跳躍表要優(yōu)于二叉樹。

? ? 本文將使用go語言實(shí)現(xiàn)跳躍表的增每界、刪捅僵、改、查操作眨层,考慮到業(yè)務(wù)上的使用場景庙楚,該實(shí)現(xiàn)將支持協(xié)程安全。

? ? 一個典型的單向四層跳躍表如圖1:

圖1.單向四層跳躍表示意圖

????由圖1可以看到谐岁,跳躍表具有以下特性:

? ? 1醋奠、層數(shù)(LEVEL)越高榛臼,鏈上的節(jié)點(diǎn)越少,大致呈P=0.5的幾何分布窜司。

? ? 2沛善、每一層的節(jié)點(diǎn)均有序且不重復(fù)。

? ? 跳躍表層數(shù)(LEVEL)的設(shè)置塞祈,應(yīng)當(dāng)與數(shù)據(jù)量息息相關(guān)金刁,合理的取值應(yīng)該為log(n),對數(shù)底數(shù)為2议薪,當(dāng)然尤蛮,考慮到數(shù)據(jù)量增長,大于log(n)也是可以的斯议。比如产捞,數(shù)據(jù)量為30億條,則合理的層數(shù)取值應(yīng)該為31<log(3000000000)<32哼御,故LEVEL應(yīng)取32坯临。

? ? 首先,定義節(jié)點(diǎn)結(jié)構(gòu)體:

type SkipListNode struct {

????key int

????data interface{}

????next []*SkipListNode

}

? ? 其中恋昼,key為排序使用的字段看靠,對應(yīng)圖1中的數(shù)字1~9;data為實(shí)際存儲的數(shù)據(jù)液肌,可以為任何類型挟炬,圖1中并沒有體現(xiàn);next為節(jié)點(diǎn)指針切片嗦哆,比如圖1中的"HEAD"節(jié)點(diǎn)的next應(yīng)該為[&5, &3, &1, &1]谤祖,“3”節(jié)點(diǎn)的next應(yīng)該為[&5, &5, &4],其中&n表示key為n節(jié)點(diǎn)的地址吝秕。

? ? 定義跳躍表結(jié)構(gòu)體:

type SkipList struct {

????head????*SkipListNode

????tail????????*SkipListNode

????length????int

? ? level? ? ? int

????mut? ? ? ?*sync.RWMutex

????rand ???? *rand.Rand

}

? ? head:頭節(jié)點(diǎn)

? ? tail:尾節(jié)點(diǎn)

? ? length: 數(shù)據(jù)總量

? ? level:層數(shù)

? ? mut:互斥鎖泊脐,用于保證協(xié)程安全

? ? rand:隨機(jī)數(shù)生成器,用于生成隨機(jī)層數(shù)烁峭,隨機(jī)生成的層數(shù)要滿足P=0.5的幾何分布容客,大致可以理解為:擲硬幣連續(xù)出現(xiàn)正面的次數(shù),就是我們要的層數(shù)约郁。所以隨機(jī)生成層數(shù)的方法可以定義如下:

func (list *SkipList) randomLevel() int {

????level := 1

????for ; level < list.level && list.rand.Uint32() & 0x1 == 1; level ++{}

????return level

}

? ? 補(bǔ)上一個工廠構(gòu)造方法:

func NewSkipList(level????int) *SkipList {

????list := &SkipList{}

????if level <=0 {

????????level =32

????}

????list.level = level

????list.head = &SkipListNode{next:make([]*SkipListNode, level, level)}

????list.tail = &SkipListNode{}

????list.mut = &sync.RWMutex{}

????list.rand = rand.New(rand.NewSource(time.Now().UnixNano()))

????for index :=range list.head.next {

????????list.head.next[index] = list.tail

????}

????return list

}

? ? 接下來實(shí)現(xiàn)數(shù)據(jù)插入方法缩挑。假設(shè)我們目前的跳躍表已經(jīng)有如下數(shù)據(jù):

圖2.需要插入數(shù)據(jù)的跳躍表

? ? 我們需要插入一個3,并且調(diào)用randomLevel得到的層數(shù)為3鬓梅,那么插入3需要如下幾部:

? ? 1供置、沿著LEVEL3的鏈查找第一個比3大的節(jié)點(diǎn)或者TAIL節(jié)點(diǎn),記錄下該節(jié)點(diǎn)的前一個節(jié)點(diǎn)——HEAD和層數(shù)——3绽快。

? ? 2芥丧、沿著LEVEL2的鏈查找第一個比3大的節(jié)點(diǎn)或者TAIL節(jié)點(diǎn)紧阔,記錄下該節(jié)點(diǎn)的前一個節(jié)點(diǎn)——&1和層數(shù)——2。

? ? 3续担、沿著LEVEL1的鏈查找第一個比3大的節(jié)點(diǎn)或者TAIL節(jié)點(diǎn)擅耽,記錄下該節(jié)點(diǎn)的前一個節(jié)點(diǎn)——&2和層數(shù)——1。

? ? 4物遇、生成一個新的節(jié)點(diǎn)newNode乖仇,key賦值為3,將newNode插入HEAD询兴、&1乃沙、&2之后,即HEAD.next[3]=&3诗舰,&1.next[2]=&3警儒,&2.next[1]=&3。

? ? 5始衅、給newNode的next賦值冷蚂,即步驟4中HEAD.next[3]缭保、&1.next[2]汛闸、2.next[1]原本的值。

? ? 注意:為了易于理解艺骂,上述步驟中所有索引均從1開始诸老,而代碼中則從0開始,所以代碼中均有索引=層數(shù)-1的關(guān)系钳恕。

? ? 那么别伏,插入的代碼可以寫成這樣:

func (list *SkipList) Add(key int, data interface{}) {

????list.mut.Lock()

????defer list.mut.Unlock()

????//1.確定插入深度

????level := list.randomLevel()

????//2.查找插入部位

????update :=make([]*SkipListNode, level, level)

????node := list.head

????for index := level -1; index >=0; index -- {

????????for {

????????????node1 := node.next[index]

????????????if node1 == list.tail || node1.key > key {

????????????????update[index] = node//找到一個插入部位

? ? ? ? ? ? ????break

? ? ? ? ???? }else if node1.key == key{

? ? ? ? ? ? ? ? node1.data = data

????????????????return

? ? ? ? ???? }else {

????????????????node = node1

????????????}

????????}

????}

????//3.執(zhí)行插入

????newNode := &SkipListNode{key, data, make([]*SkipListNode, level, level)}

????for index, node :=range update {

????????node.next[index],newNode.next[index] = newNode,node.next[index]

????}

????list.length ++

}

? ? 實(shí)際上,刪除忧额、查找的方法與插入變化不大厘肮。

? ? 刪除方法:

func (list *SkipList) Remove(key int) bool {

????list.mut.Lock()

????defer list.mut.Unlock()

//1.查找刪除節(jié)點(diǎn)

????node := list.head

????remove :=make([]*SkipListNode, list.level, list.level)

????var target *SkipListNode

????for index :=len(node.next) -1; index >=0; index -- {

????????for {

????????????node1 := node.next[index]

????????????if node1 == list.tail ||? node1.key >? key{

????????????????break

? ? ? ? ???? }else if node1.key == key {

????????????????remove[index] = node//找到啦

? ? ? ? ? ? ????target = node1

????????????????break

? ? ? ? ???? }else {

????????????????node = node1

????????????}

????????}

????}

//2.執(zhí)行刪除

????if target != nil {

????????for index, node1 :=range remove {

????????????if node1 != nil {

????????????????node1.next[index] = target.next[index]

????????????}

????????}

????????list.length --

????????return true

? ???? }

????return false

}

? ? 查找方法:

func (list *SkipList)Find(key int) interface{} {

????list.mut.RLock()

????defer list.mut.RUnlock()

????node := list.head

????for index :=len(node.next) -1; index >=0; index -- {

????????for {

????????????node1 := node.next[index]

????????????if node1 == list.tail ||? node1.key >? key? ?{

????????????????break

? ? ? ? ???? }else if node1.key == key {

????????????????return node1.data

????????????}else {

????????????????node = node1

????????????}

????????}

????}

return nil

}

? ? 最后,是獲取數(shù)據(jù)總量方法:

func (list *SkipList) Length() int {

????list.mut.RLock()

????defer list.mut.RUnlock()

????return list.length

}

? ? 就這樣睦番,一個單向的跳躍表就實(shí)現(xiàn)了类茂,是不是很簡單呢?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末托嚣,一起剝皮案震驚了整個濱河市巩检,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌示启,老刑警劉巖兢哭,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異夫嗓,居然都是意外死亡迟螺,警方通過查閱死者的電腦和手機(jī)冲秽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來矩父,“玉大人劳跃,你說我怎么就攤上這事≌愕妫” “怎么了刨仑?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長夹姥。 經(jīng)常有香客問我杉武,道長,這世上最難降的妖魔是什么辙售? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任轻抱,我火速辦了婚禮,結(jié)果婚禮上旦部,老公的妹妹穿的比我還像新娘祈搜。我一直安慰自己,他們只是感情好士八,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布容燕。 她就那樣靜靜地躺著,像睡著了一般婚度。 火紅的嫁衣襯著肌膚如雪蘸秘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天蝗茁,我揣著相機(jī)與錄音醋虏,去河邊找鬼。 笑死哮翘,一個胖子當(dāng)著我的面吹牛颈嚼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播饭寺,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼阻课,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了佩研?” 一聲冷哼從身側(cè)響起柑肴,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎旬薯,沒想到半個月后晰骑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年硕舆,在試婚紗的時候發(fā)現(xiàn)自己被綠了秽荞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡抚官,死狀恐怖扬跋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情凌节,我是刑警寧澤钦听,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站倍奢,受9級特大地震影響朴上,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卒煞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一痪宰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧畔裕,春花似錦衣撬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至贴届,卻和暖如春靠粪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背毫蚓。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昔善,地道東北人元潘。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像君仆,于是被迫代替她去往敵國和親翩概。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容