【數(shù)據(jù)結(jié)構(gòu)與算法】鏈表

一臀防、是什么

一種物理存儲(chǔ)單元(內(nèi)存)非連續(xù)的數(shù)據(jù)結(jié)構(gòu)遂蛀,數(shù)據(jù)元素的邏輯順序通過鏈表中的指針依次串聯(lián)


二进肯、使用場景

  • RedisLRU緩存淘汰策略
    LRU:Least Recently Used岂傲,代表最近最久未使用痰洒,使用的立馬更新成最新的麦向,剔除近段時(shí)間最久沒更新的數(shù)據(jù)口糕。它是按照一個(gè)非常著名的計(jì)算機(jī)操作系統(tǒng)理論:最近使用的頁面數(shù)據(jù)會(huì)在未來一段時(shí)期內(nèi)仍然被使用,已經(jīng)很久沒有使用的頁面很有可能在未來較長的一段時(shí)間內(nèi)仍然不會(huì)被使用磕蛇。
  • 約瑟夫環(huán)算法題
  • 頻繁更新景描、刪除的場景

三、工作原理

每個(gè)數(shù)據(jù)結(jié)點(diǎn)秀撇,除了存儲(chǔ)數(shù)據(jù)之外超棺,還需要記錄下一個(gè)結(jié)點(diǎn)的地址


四、鏈表類型

  • 單鏈表:
    每個(gè)數(shù)據(jù)結(jié)點(diǎn)除存儲(chǔ)數(shù)據(jù)呵燕,還記錄下個(gè)結(jié)點(diǎn)的地址(后繼指針)
  • 雙鏈表:
    每個(gè)數(shù)據(jù)結(jié)點(diǎn)除了存儲(chǔ)數(shù)據(jù)棠绘,還記錄上一個(gè)和下一個(gè)結(jié)點(diǎn)的地址(前繼指針和后繼指針)
  • 循環(huán)鏈表:
    單鏈表的尾結(jié)點(diǎn)指針指向NULL,循環(huán)鏈表的尾結(jié)點(diǎn)指向鏈表的頭結(jié)點(diǎn)
  • 循環(huán)雙鏈表:
    循環(huán)鏈表和雙鏈表的結(jié)合

PS:

  1. 頭結(jié)點(diǎn): 頭結(jié)點(diǎn)的數(shù)據(jù)為空,只記錄鏈表的基地址
  2. 尾結(jié)點(diǎn): 尾結(jié)點(diǎn)的數(shù)據(jù)不為空再扭,指針指向一個(gè)空地址NULL

五氧苍、實(shí)現(xiàn)

  • 代碼實(shí)現(xiàn)
package main

import "fmt"

// 數(shù)據(jù)結(jié)點(diǎn)
type node struct {
    data int
    prev *node
    next *node
}

// 在 xxx 結(jié)點(diǎn)之前插入結(jié)點(diǎn)
func InsertBeforeLinkedList(a int, beforeInsert *node) *node {
    insertNode := node{
        data: a,
        prev: beforeInsert.prev,
        next: beforeInsert,
    }
    beforeInsert.prev.next = &insertNode
    beforeInsert.prev = &insertNode

    return &insertNode
}

// 在 xxx 結(jié)點(diǎn)之后插入結(jié)點(diǎn)
func InsertAfterLinkedList(a int, afterInsert *node) *node {
    insertNode := node{
        data: a,
        prev: afterInsert,
        next: afterInsert.next,
    }
    // 校驗(yàn)是否是第一個(gè)結(jié)點(diǎn),首結(jié)點(diǎn)無 next
    if afterInsert.next != nil {
        afterInsert.next.prev = &insertNode
    }
    afterInsert.next = &insertNode

    return &insertNode
}

func main() {
    head := node{}
    zero := node{
        prev: &head,
        next: nil,
    }

    /*** 在 xxx 結(jié)點(diǎn)之前插入結(jié)點(diǎn)驗(yàn)證 ***/
    // zero 的前面增加結(jié)點(diǎn)-1
    before := InsertBeforeLinkedList(-1, &zero)
    // head 在 before 前面 before.prev
    fmt.Printf("%p", &head)
    // before 結(jié)構(gòu)體
    fmt.Println(before)
    // zero 在 before 后面 before.next
    fmt.Printf("%p", &zero)

    /*** 在 xxx 結(jié)點(diǎn)之后插入結(jié)點(diǎn)驗(yàn)證 ***/
    zero = node{
        prev: &head,
        next: nil,
    }
    // zero 的后面加結(jié)點(diǎn)1
    one := InsertAfterLinkedList(1, &zero)
    // zero.pre
    fmt.Printf("%p", &head)
    // zero 結(jié)構(gòu)體的值
    fmt.Println(zero)
    // zero.next
    fmt.Printf("%p", one)

    return
}

六泛范、優(yōu)劣

  • 優(yōu)點(diǎn):
  1. 合理利用碎片內(nèi)存空間
  2. 一定的先決條件下让虐,插入和刪除操作時(shí)間復(fù)雜度為 O(1)
先決條件
插入:向a地址之前插入一條數(shù)據(jù),需要知道a地址之前的結(jié)點(diǎn)地址
刪除:刪除a地址的數(shù)據(jù)罢荡,需要知道a地址之前的結(jié)點(diǎn)數(shù)據(jù)

PS:雙鏈表情況下可以不需要此先決條件
  • 缺點(diǎn):
  1. 隨機(jī)訪問的性能沒有數(shù)組好赡突,需要O(n)的時(shí)間復(fù)雜度

七、替代性技術(shù)

  • 數(shù)組
  • 隊(duì)列

八区赵、經(jīng)典應(yīng)用

  • LRU算法
package main

import "fmt"

type Node struct {
    Key   int
    Vaule int
    prev  *Node
    next  *Node
}

type LRUCache struct {
    limit   int
    hashMap map[int]*Node
    head    *Node
    end     *Node
}

// 初始化緩存
func Constructor(size int) *LRUCache {
    newCache := LRUCache{
        limit:   size,
        hashMap: make(map[int]*Node, size),
    }

    return &newCache
}

// 獲取緩存數(shù)據(jù)
//  1惭缰、判斷數(shù)據(jù)是否存在
//      1、不存在笼才,直接返回
//      2漱受、存在,則數(shù)據(jù)刷新骡送,返回?cái)?shù)據(jù)
func (l *LRUCache) get(key int) int {
    if v, ok := l.hashMap[key]; !ok {
        return -1
    } else {
        l.refreshData(v)
        return v.Vaule
    }
}

// 寫入數(shù)據(jù)
//
//  1昂羡、 判斷數(shù)據(jù)是否存在
//      1絮记、存在,則數(shù)據(jù)更新紧憾、刷新
//      2到千、不存在昌渤,判斷是否緩存已滿
//          1赴穗、已滿,則移除頭數(shù)據(jù)膀息,新數(shù)據(jù)直接放在最后
//          2般眉、未滿,新數(shù)據(jù)直接放到最后

func (l *LRUCache) put(key, value int) int {
    // 判斷數(shù)據(jù)是否存在
    if v, ok := l.hashMap[key]; ok {
        // 存在則更新潜支、刷新數(shù)據(jù)
        v.Vaule = value
        l.refreshData(v)
        return 1
    } else {
        // 判斷緩存是否已滿
        if len(l.hashMap) >= l.limit {
            l.removeData(l.head)
            delete(l.hashMap, key)
        }
        newData := &Node{
            Key:   key,
            Vaule: value,
        }
        l.addData(newData)
        l.hashMap[key] = newData
        return 1
    }
}

// 刷新
//  1甸赃、數(shù)據(jù)刪除
//  2、數(shù)據(jù)放到最后
func (l *LRUCache) refreshData(dataNode *Node) {
    l.removeData(dataNode)
    l.addData(dataNode)
}

//移除數(shù)據(jù)
//1冗酿、判斷緩存中是否存在此數(shù)據(jù)
//  1埠对、不存在,則直接 return
//  2裁替、存在项玛,則分3種情況
//      1、緩存頭數(shù)據(jù)弱判,緩存頭直接指向 head.next
//      2襟沮、緩存尾數(shù)據(jù),緩存尾直接指向 end.prev
//      3昌腰、非緩存頭尾數(shù)據(jù)
func (l *LRUCache) removeData(dataNode *Node) (position int) {
    // 判斷緩存中數(shù)據(jù)是否存在
    if _, ok := l.hashMap[dataNode.Key]; !ok {
        return -1
    } else {
        if l.head == dataNode { // 緩存頭數(shù)據(jù)
            l.head = l.head.next
            l.head.prev = nil
        } else if l.end == dataNode { // 緩存尾數(shù)據(jù)
            l.end = l.end.prev
            l.end.next = nil
        } else {
            dataNode.prev.next = dataNode.next
            dataNode.next.prev = dataNode.prev
        }

        return 1
    }
}

//添加數(shù)據(jù)开伏,放在最后
//1、判斷當(dāng)前數(shù)據(jù)是不是就是最后的數(shù)據(jù)
//  1遭商、就是最后數(shù)據(jù)固灵,無需操作
//  2、非最后數(shù)據(jù)劫流,判斷當(dāng)前緩存是否為空
//      1怎虫、為空,則直接放數(shù)據(jù)
//      2困介、非空大审,進(jìn)行數(shù)據(jù)指針切換
func (l *LRUCache) addData(dataNode *Node) {
    if l.end != dataNode {
        if l.end == nil {
            l.head = dataNode
            l.end = dataNode
            l.end.next = nil
        } else {
            dataNode.prev = l.end
            l.end.next = dataNode
            l.end = dataNode
            l.end.next = nil
        }
    }
}

// 依序獲取整個(gè)鏈表的數(shù)據(jù)
// 測試用
func (l *LRUCache) getCache() {
    pos := l.head
    for {
        fmt.Printf("%p", pos)
        fmt.Println(pos)
        if pos.next == nil {
            break
        }
        pos = pos.next
    }
}

func main() {
    cache := Constructor(3)
    cache.put(11, 1)
    cache.put(22, 2)
    cache.put(33, 3)
    cache.put(44, 4)
    cache.put(55, 5)
    cache.getCache()
    cache.get(33)
    fmt.Println("========== 獲取數(shù)據(jù)之后 ===============")
    cache.getCache()
}

  • 約瑟夫環(huán)
package main

import "fmt"

// 人
type Person struct {
    num  int
    prev *Person
    next *Person
}

// 環(huán)
type Roll struct {
    size         int
    moveInt      int
    targetPerson int
    head         *Person
    end          *Person
    hashMap      map[int]*Person
}

// 初始化緩存
func Constructor(size, moveInt, targetPerson int) *Roll {
    r := &Roll{
        size:         size,
        moveInt:      moveInt,
        hashMap:      make(map[int]*Person),
        targetPerson: targetPerson,
    }
    for i, tmpPerson := 1, r.head; i <= size; i++ {
        // 頭鏈表
        if i == 1 {
            r.head = &Person{
                num: i,
            }
            r.hashMap[i] = r.head
            tmpPerson = r.head
            continue
        }
        // 尾鏈表
        if i == size {
            r.end = &Person{
                num:  size,
                next: r.head,
                prev: tmpPerson,
            }
            tmpPerson.next = r.end
            r.head.prev = r.end
            r.hashMap[size] = r.end
            break
        }
        tmp := &Person{
            num:  i,
            prev: tmpPerson,
        }
        r.hashMap[i] = tmp
        tmpPerson.next = tmp
        tmpPerson = tmp
    }

    return r
}

// 依序獲取整個(gè)鏈表的數(shù)據(jù)
// 測試用
func (r *Roll) getRoll(size int) {
    pos := r.head
    for i := 1; i <= size; i++ {
        fmt.Println(i)
        fmt.Printf("%p", pos)
        fmt.Println(pos)
        if pos.next != nil {
            pos = pos.next
        }
    }
}

// 移除人
func (r *Roll) remove(removePerson *Person) (nextPerson *Person) {
    fmt.Println(removePerson.num)
    nextPerson = removePerson.next
    removePerson.prev.next = removePerson.next
    removePerson.next.prev = removePerson.prev
    delete(r.hashMap, removePerson.num)
    return
}

// 循環(huán)
// 進(jìn)行循環(huán),符合條件的進(jìn)行移除座哩,直至環(huán)剩下的人數(shù)恰好等于目標(biāo)人數(shù)
func (r *Roll) round() {
    removePerson := r.head
    i := 1
    for {
        // 判斷環(huán)的大小是否等于目標(biāo)大小
        if len(r.hashMap) <= r.targetPerson || len(r.hashMap) == 0 {
            break
        }
        // 判斷是否到指定游標(biāo)的人
        if i == r.moveInt {
            removePerson = r.remove(removePerson)
            i = 1
        } else {
            i++
            removePerson = removePerson.next
        }
    }
}

func main() {
    roll := Constructor(1, 5, 2)
    roll.round()
}

  • 回文字符串
package main

import (
    "fmt"
)

func findStrMiddle(str string) bool {
    // 字符串轉(zhuǎn) byte 切片
    runeStr := []byte(str)
    firstStr := []byte{}
    // 字符串長度
    length := len(runeStr)
    for i, j, k := 0, 0, 0; i < len(runeStr); i++ {
        if j < len(str) { // first 字符串前半部分
            firstStr = append(firstStr, runeStr[i])
            j += 2
        } else { // 逆序從字符串尾部徒扶,依次與字符串前半部分一一比較
            if firstStr[k] != runeStr[length-1] {
                return false
            } else {
                length -= 1
                k++
            }
        }
    }

    return true
}

func main() {
    fmt.Println(findStrMiddle("level"))
}
  • 鏈表反轉(zhuǎn)
// 輸入: a->b->c->d->e->NULL
// 輸出: e->d->c->b->a->NULL
package main

import "fmt"

type Node struct {
    data string
    next *Node
}

// 初始化字符串鏈表
func (head *Node) Constructor(str string) {
    byteStr := []byte(str)
    cur := head
    for i := 0; i < len(byteStr); i++ {
        cur.next = &Node{data: string(byteStr[i])}
        cur = cur.next
    }
}

// 反轉(zhuǎn)鏈表
func (head *Node) reverseLinkedList() (newHead *Node) {
    cur := head
    var pre *Node
    for cur != nil {
        cur.next, pre, cur = pre, cur, cur.next
    }

    return pre
}

// 循環(huán)輸出鏈表,測試用
func getLinkedList(node *Node) {
    for cur := node; cur != nil; cur = cur.next {
        fmt.Printf("%p", cur)
        fmt.Println(cur)
    }
}

func main() {
    head := &Node{}
    head.Constructor("abcdefg")
    getLinkedList(head.next)
    newHead := head.next.reverseLinkedList()
    fmt.Println("-----------------------")
    getLinkedList(newHead)
}

  • 有序鏈表合并
package main

import "fmt"

type ListNode struct {
    Val  int
    Next *ListNode
}

func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
    head := &ListNode{}
    // 校驗(yàn) l1/l2 是否為空
    if l1 == nil && l2 != nil {
        return l2
    }
    if l2 == nil && l1 != nil {
        return l1
    }
    if l2 == nil && l1 == nil {
        return nil
    }
    if l1.Val > l2.Val {
        addNode(l1, l2, head)
    } else {
        addNode(l2, l1, head)
    }
    return head.Next
}

// l1.val > l2.val
func addNode(max *ListNode, min *ListNode, node *ListNode) {
    node.Next = min
    // 終止條件
    if min.Next == nil {
        node.Next.Next = max
        return
    }
    if max.Val > min.Next.Val {
        addNode(max, min.Next, node.Next)
    } else {
        addNode(min.Next, max, node.Next)
    }
}

// 初始化鏈表
func (head *ListNode) constructor(nums []int) {
    cur := head
    for _, v := range nums {
        cur.Next = &ListNode{
            Val: v,
        }
        cur = cur.Next
    }
}

// 輸出鏈表
func (head *ListNode) printLinkedList() {
    for cur := head; cur != nil; cur = cur.Next {
        fmt.Println(cur.Val)
    }
}

func main() {
    head1 := &ListNode{}
    head1.constructor([]int{})
    head1.Next.printLinkedList()
    fmt.Println("-------------------------")
    head2 := &ListNode{}
    head2.constructor([]int{})
    head2.Next.printLinkedList()
    fmt.Println("-------------------------")
    merge := mergeTwoLists(head1.Next, head2.Next)
    merge.printLinkedList()
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末根穷,一起剝皮案震驚了整個(gè)濱河市姜骡,隨后出現(xiàn)的幾起案子导坟,更是在濱河造成了極大的恐慌,老刑警劉巖圈澈,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惫周,死亡現(xiàn)場離奇詭異,居然都是意外死亡康栈,警方通過查閱死者的電腦和手機(jī)递递,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啥么,“玉大人登舞,你說我怎么就攤上這事⌒伲” “怎么了菠秒?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長氯迂。 經(jīng)常有香客問我践叠,道長,這世上最難降的妖魔是什么嚼蚀? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任禁灼,我火速辦了婚禮,結(jié)果婚禮上驰坊,老公的妹妹穿的比我還像新娘匾二。我一直安慰自己,他們只是感情好拳芙,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布察藐。 她就那樣靜靜地躺著,像睡著了一般舟扎。 火紅的嫁衣襯著肌膚如雪分飞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天睹限,我揣著相機(jī)與錄音譬猫,去河邊找鬼。 笑死羡疗,一個(gè)胖子當(dāng)著我的面吹牛染服,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叨恨,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼柳刮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起秉颗,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤痢毒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蚕甥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哪替,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年菇怀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凭舶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡敏释,死狀恐怖库快,靈堂內(nèi)的尸體忽然破棺而出摸袁,到底是詐尸還是另有隱情钥顽,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布靠汁,位于F島的核電站蜂大,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蝶怔。R本人自食惡果不足惜奶浦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望踢星。 院中可真熱鬧澳叉,春花似錦、人聲如沸沐悦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽藏否。三九已至瓶殃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間副签,已是汗流浹背遥椿。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留淆储,地道東北人冠场。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像本砰,于是被迫代替她去往敵國和親碴裙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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