go-數據結構

来源:https://www.cnblogs.com/ygjzs/archive/2019/11/27/11945699.html
-Advertisement-
Play Games

數據結構 數據結構(演算法)的介紹 數據結構的介紹 1) 數據結構是一門研究演算法的學科,只從有了編程語言也就有了數據結構.學好數據結構可以編寫 出更加漂亮,更加有效率的代碼。 2) 要學習好數據結構就要多多考慮如何將生活中遇到的問題,用程式去實現解決. 3) 程式 = 數據結構 + 演算法 20.2 數 ...


數據結構

數據結構(演算法)的介紹

數據結構的介紹

1) 數據結構是一門研究演算法的學科,只從有了編程語言也就有了數據結構.學好數據結構可以編寫
出更加漂亮,更加有效率的代碼。
2) 要學習好數據結構就要多多考慮如何將生活中遇到的問題,用程式去實現解決.
3) 程式 = 數據結構 + 演算法
20.2 數據結構和演算法的關係

演算法是程式的靈魂。

為什麼有些網站能夠在高併發,和海量吞吐情況下依然堅如磐石,大家可能會
說: 網站使用了伺服器群集技術、資料庫讀寫分離和緩存技術(比如 Redis 等),那如果我再深入的問
一句,這些優化技術又是怎樣被那些天才的技術高手設計出來的呢?
大家請思考一個問題,是什麼讓不同的人寫出的代碼從功能看是一樣的,但從效率上卻有天壤之別

曾經我聽有人是這麼說的:" 我是做伺服器的,環境是 UNIX,功能是要支持上千萬人同時線上,
並保證數據傳輸的穩定, 在伺服器上線前,做內測,一切 OK,可上線後,伺服器就支撐不住了, 公
司的 CTO 對我的代碼進行優化,再次上線,堅如磐石。那一瞬間,我認識到程式是有靈魂的,就是
演算法。拿在公司工作的實際經歷來說,如果你不想永遠都是代碼工人,那就花時間來研究下演算法吧!"

稀疏 sparsearray 數組

先看一個實際的需求

  • 編寫的五子棋程式中,有存檔退出和續上盤的功能
  • 分析按照原始的方式來的二維數組的問題因為該二維數組的很多值是預設值 0, 因此記錄了很多沒有意義的數據

基本介紹

當一個數組中大部分元素為0,或者為同一個值的數組時,可以使用稀疏數組來保存該數組。
稀疏數組的處理方法是:
1) 記錄數組一共有幾行幾列,有多少個不同的值
2) 思想:把具有不同值的元素的行列及值記錄在一個小規模的數組中,從而 縮小程式的規模

應用實例

1) 使用稀疏數組,來保留類似前面的二維數組(棋盤、地圖等等)
2) 把稀疏數組存檔,並且可以從新恢複原來的二維數組數

package main
import (
    "fmt"
)

type ValNode struct {
    row int
    col int
    val int
} 

func main() {

    //1. 先創建一個原始數組
    var chessMap [11][11]int
    chessMap[1][2] = 1 //黑子
    chessMap[2][3] = 2 //藍子

    //2. 輸出看看原始的數組
    for _, v := range chessMap {
        for _, v2 := range v {
            fmt.Printf("%d\t", v2)
        }
        fmt.Println()
    } 

    //3. 轉成稀疏數組。想-> 演算法
    // 思路
    //(1). 遍歷 chessMap, 如果我們發現有一個元素的值不為0,創建一個node結構體
    //(2). 將其放入到對應的切片即可

    var sparseArr []ValNode
    
    //標準的一個稀疏數組應該還有一個 記錄元素的二維數組的規模(行和列,預設值)
    //創建一個ValNode 值結點
    valNode := ValNode{
        row : 11,
        col : 11,
        val : 0,
    }

    sparseArr = append(sparseArr, valNode)

    for i, v := range chessMap {
        for j, v2 := range v {
            if v2 != 0 {
                //創建一個ValNode 值結點
                valNode := ValNode{
                    row : i,
                    col : j,
                    val : v2,
                }
                sparseArr = append(sparseArr, valNode)
            }   
        }
        
    }
    
    //輸出稀疏數組
    fmt.Println("當前的稀疏數組是:::::")
    for i, valNode := range sparseArr {
        fmt.Printf("%d: %d %d %d\n", i, valNode.row, valNode.col, valNode.val)
    }

    //將這個稀疏數組,存檔 d:/chessmap.data

    //如何恢複原始的數組

    //1. 打開這個d:/chessmap.data => 恢複原始數組.

    //2. 這裡使用稀疏數組恢復


    // 先創建一個原始數組
    var chessMap2 [11][11]int 

    // 遍歷 sparseArr [遍歷文件每一行]
    for i, valNode := range sparseArr {
        if i != 0 { //跳過第一行記錄值
            chessMap2[valNode.row][valNode.col] = valNode.val
        }
    }

    // 看看chessMap2 是不是恢復.
    fmt.Println("恢復後的原始數據......")
    for _, v := range chessMap2 {
        for _, v2 := range v {
            fmt.Printf("%d\t", v2)
        }
        fmt.Println()
    }
}

隊列(queue)

隊列介紹

  • 隊列是一個有序列表,可以用 數組或是 鏈表來實現。
  • 遵循 先入先出的原則。即:先存入隊列的數據,要先取出。後存入的要後取出

數組模擬隊列

package main
import (
    "fmt"
    "os"
    "errors"
)

//使用一個結構體管理隊列
type Queue struct {
    maxSize int 
    array [5]int // 數組=>模擬隊列
    front int // 表示指向隊列首
    rear int // 表示指向隊列的尾部
}

//添加數據到隊列
func (this *Queue) AddQueue(val int) (err error) {

    //先判斷隊列是否已滿
    if this.rear == this.maxSize - 1 { //重要重要的提示; rear 是隊列尾部(含最後元素)
        return errors.New("queue full")
    }

    this.rear++ //rear 後移
    this.array[this.rear] = val
    return 
}

//從隊列中取出數據
func (this *Queue) GetQueue() (val int, err error) {
    //先判斷隊列是否為空
    if this.rear == this.front { //隊空
        return -1, errors.New("queue empty")
    }
    this.front++
    val = this.array[this.front]
    return val ,err 
}

//顯示隊列, 找到隊首,然後到遍歷到隊尾
//
func (this *Queue) ShowQueue() {
    fmt.Println("隊列當前的情況是:")
    //this.front 不包含隊首的元素
    for i := this.front + 1; i <= this.rear; i++ {
        fmt.Printf("array[%d]=%d\t", i, this.array[i])
    }
    fmt.Println()
}



//編寫一個主函數測試,測試
func main() {

    //先創建一個隊列
    queue := &Queue{
        maxSize : 5,
        front : -1,
        rear : -1,

    }

    var key string 
    var val int
    for {
        fmt.Println("1. 輸入add 表示添加數據到隊列")
        fmt.Println("2. 輸入get 表示從隊列獲取數據")
        fmt.Println("3. 輸入show 表示顯示隊列")
        fmt.Println("4. 輸入exit 表示顯示隊列")

        fmt.Scanln(&key)
        switch key {
            case "add":
                fmt.Println("輸入你要入隊列數")
                fmt.Scanln(&val)
                err := queue.AddQueue(val)
                if err != nil {
                    fmt.Println(err.Error())
                } else {

                    fmt.Println("加入隊列ok")
                }
            case "get":
                val, err := queue.GetQueue()
                if err != nil {
                    fmt.Println(err.Error())
                } else {
                    fmt.Println("從隊列中取出了一個數=", val)
                }
            case "show":
                queue.ShowQueue()
            case "exit":
                os.Exit(0)
        }
    }
}

數組模擬環形隊列

package main
import (
    "fmt"
    "errors"
    "os"
)

//使用一個結構體管理環形隊列
type CircleQueue struct {
    maxSize int // 4
    array [5]int // 數組
    head  int //指向隊列隊首 0
    tail int  //指向隊尾 0
}


//如隊列 AddQueue(push)  GetQueue(pop)
//入隊列
func (this *CircleQueue) Push(val int)  (err error) {
    if this.IsFull() {
        return errors.New("queue full")
    }
    //分析出this.tail 在隊列尾部,但是包含最後的元素
    this.array[this.tail] = val //把值給尾部
    this.tail = (this.tail + 1) % this.maxSize
    return 
}

//出隊列
func (this *CircleQueue) Pop() (val int, err error) {

    if this.IsEmpty() {
        return 0, errors.New("queue empty")
    }
    //取出,head 是指向隊首,並且含隊首元素
    val = this.array[this.head]
    this.head = (this.head + 1) % this.maxSize
    return 
}

//顯示隊列
func (this *CircleQueue) ListQueue() {

    fmt.Println("環形隊列情況如下:")
    //取出當前隊列有多少個元素
    size := this.Size()
    if size == 0 {
        fmt.Println("隊列為空")
    }

    //設計一個輔助的變數,指向head
    tempHead := this.head
    for i := 0; i < size; i++ {
        fmt.Printf("arr[%d]=%d\t", tempHead, this.array[tempHead])
        tempHead = (tempHead + 1) % this.maxSize
    }
    fmt.Println()
}

//判斷環形隊列為滿
func (this *CircleQueue) IsFull() bool {
    return (this.tail + 1) % this.maxSize == this.head 
}
//判斷環形隊列是空
func (this *CircleQueue) IsEmpty() bool {
    return this.tail == this.head 
}
//取出環形隊列有多少個元素
func (this *CircleQueue) Size() int {
    //這是一個關鍵的演算法.
    return (this.tail + this.maxSize - this.head ) % this.maxSize
}


func main() {

    //初始化一個環形隊列
    queue := &CircleQueue{
        maxSize : 5,
        head : 0,
        tail : 0,
    }

    var key string 
    var val int
    for {
        fmt.Println("1. 輸入add 表示添加數據到隊列")
        fmt.Println("2. 輸入get 表示從隊列獲取數據")
        fmt.Println("3. 輸入show 表示顯示隊列")
        fmt.Println("4. 輸入exit 表示顯示隊列")

        fmt.Scanln(&key)
        switch key {
            case "add":
                fmt.Println("輸入你要入隊列數")
                fmt.Scanln(&val)
                err := queue.Push(val)
                if err != nil {
                    fmt.Println(err.Error())
                } else {

                    fmt.Println("加入隊列ok")
                }
            case "get":
                val, err := queue.Pop()
                if err != nil {
                    fmt.Println(err.Error())
                } else {
                    fmt.Println("從隊列中取出了一個數=", val)
                }
            case "show":
                queue.ListQueue()
            case "exit":
                os.Exit(0)
        }
    }
}

鏈表

鏈表介紹

鏈表是有序的列表

單鏈表的介紹

一般來說,為了比較好的對單鏈表進行增刪改查的操作,我們都會給他設置一個頭結點, 頭
結點的作用主要是用來標識鏈表頭, 本身這個結點不存放數據。

單鏈表的應用實例
案例的說明:

使用帶 head 頭的單向鏈表實現 –水滸英雄排行榜管理
完成對英雄人物的增刪改查操作

第一種方法在添加英雄時,直接添加到鏈表的尾部

package main
import (
    "fmt"
)

//定義一個HeroNode
type HeroNode struct {
    no       int
    name     string
    nickname string
    next     *HeroNode //這個表示指向下一個結點
}

//給鏈表插入一個結點
//編寫第一種插入方法,在單鏈表的最後加入.[簡單]
func InsertHeroNode(head *HeroNode, newHeroNode *HeroNode) {
    //思路
    //1. 先找到該鏈表的最後這個結點
    //2. 創建一個輔助結點[跑龍套, 幫忙]
    temp := head
    for {
        if temp.next == nil { //表示找到最後
            break
        }
        temp = temp.next // 讓temp不斷的指向下一個結點
    }

    //3. 將newHeroNode加入到鏈表的最後
    temp.next = newHeroNode
}

//給鏈表插入一個結點
//編寫第2種插入方法,根據no 的編號從小到大插入..【實用】
func InsertHeroNode2(head *HeroNode, newHeroNode *HeroNode) {
    //思路
    //1. 找到適當的結點
    //2. 創建一個輔助結點[跑龍套, 幫忙]
    temp := head
    flag := true
    //讓插入的結點的no,和temp的下一個結點的no比較
    for {
        if temp.next == nil {//說明到鏈表的最後
            break
        } else if temp.next.no >= newHeroNode.no {
            //說明newHeroNode 就應該插入到temp後面
            break 
        } else if temp.next.no == newHeroNode.no {
            //說明我們額鏈表中已經有這個no,就不然插入.
            flag = false
            break
        }
        temp = temp.next
    }

    if !flag {
        fmt.Println("對不起,已經存在no=", newHeroNode.no)
        return
    } else {
        newHeroNode.next = temp.next
        temp.next = newHeroNode
    }


}
//刪除一個結點
func DelHerNode(head *HeroNode, id int) {
    temp := head
    flag := false
    //找到要刪除結點的no,和temp的下一個結點的no比較
    for {
        if temp.next == nil {//說明到鏈表的最後
            break
        } else if temp.next.no == id {
            //說明我們找到了.
            flag = true
            break
        }
        temp = temp.next
    }
    if flag {//找到, 刪除

        temp.next = temp.next.next
    } else {
        fmt.Println("sorry, 要刪除的id不存在")
    }
}

//顯示鏈表的所有結點信息
func ListHeroNode(head *HeroNode) {

    //1. 創建一個輔助結點[跑龍套, 幫忙]
    temp := head

    // 先判斷該鏈表是不是一個空的鏈表
    if temp.next == nil {
        fmt.Println("空空如也。。。。")
        return 
    }
    //2. 遍歷這個鏈表
    for {
        fmt.Printf("[%d , %s , %s]==>", temp.next.no, 
            temp.next.name, temp.next.nickname)
        //判斷是否鏈表後
        temp = temp.next
        if temp.next == nil {
            break
        }
    }
}




func main() {

    //1. 先創建一個頭結點, 
    head := &HeroNode{}

    //2. 創建一個新的HeroNode
    hero1 := &HeroNode{
        no : 1,
        name : "宋江",
        nickname : "及時雨",
    }

    hero2 := &HeroNode{
        no : 2,
        name : "盧俊義",
        nickname : "玉麒麟",
    }

    hero3 := &HeroNode{
        no : 3,
        name : "林沖",
        nickname : "豹子頭",
    }

    // hero4 := &HeroNode{
    //  no : 3,
    //  name : "吳用",
    //  nickname : "智多星",
    // }

    //3. 加入
    InsertHeroNode2(head, hero3)
    InsertHeroNode2(head, hero1)
    InsertHeroNode2(head, hero2)
    
    // 4. 顯示
    ListHeroNode(head)

    // 5 刪除
    fmt.Println()
    DelHerNode(head, 1)
    DelHerNode(head, 3)
    ListHeroNode(head)
}
    

雙向鏈表的應用實例

package main
import (
    "fmt"
)

//定義一個HeroNode
type HeroNode struct {
    no       int
    name     string
    nickname string
    pre      *HeroNode //這個表示指向前一個結點
    next     *HeroNode //這個表示指向下一個結點
}

//給雙向鏈表插入一個結點
//編寫第一種插入方法,在單鏈表的最後加入.[簡單]
func InsertHeroNode(head *HeroNode, newHeroNode *HeroNode) {
    //思路
    //1. 先找到該鏈表的最後這個結點
    //2. 創建一個輔助結點[跑龍套, 幫忙]
    temp := head
    for {
        if temp.next == nil { //表示找到最後
            break
        }
        temp = temp.next // 讓temp不斷的指向下一個結點
    }

    //3. 將newHeroNode加入到鏈表的最後
    temp.next = newHeroNode
    newHeroNode.pre = temp
}

//給雙向鏈表插入一個結點
//編寫第2種插入方法,根據no 的編號從小到大插入..【實用】
func InsertHeroNode2(head *HeroNode, newHeroNode *HeroNode) {
    //思路
    //1. 找到適當的結點
    //2. 創建一個輔助結點[跑龍套, 幫忙]
    temp := head
    flag := true
    //讓插入的結點的no,和temp的下一個結點的no比較
    for {
        if temp.next == nil {//說明到鏈表的最後
            break
        } else if temp.next.no >= newHeroNode.no {
            //說明newHeroNode 就應該插入到temp後面
            break 
        } else if temp.next.no == newHeroNode.no {
            //說明我們額鏈表中已經有這個no,就不然插入.
            flag = false
            break
        }
        temp = temp.next
    }

    if !flag {
        fmt.Println("對不起,已經存在no=", newHeroNode.no)
        return
    } else {
        newHeroNode.next = temp.next //ok
        newHeroNode.pre = temp//ok
        if temp.next != nil {
            temp.next.pre = newHeroNode //ok
        }
        temp.next = newHeroNode //ok
    }


}
//刪除一個結點[雙向鏈表刪除一個結點]
func DelHerNode(head *HeroNode, id int) {
    temp := head
    flag := false
    //找到要刪除結點的no,和temp的下一個結點的no比較
    for {
        if temp.next == nil {//說明到鏈表的最後
            break
        } else if temp.next.no == id {
            //說明我們找到了.
            flag = true
            break
        }
        temp = temp.next
    }
    if flag {//找到, 刪除
        temp.next = temp.next.next //ok
        if temp.next != nil {
            temp.next.pre = temp 
        }
    } else {
        fmt.Println("sorry, 要刪除的id不存在")
    }
}

//顯示鏈表的所有結點信息
//這裡仍然使用單向的鏈表顯示方式
func ListHeroNode(head *HeroNode) {

    //1. 創建一個輔助結點[跑龍套, 幫忙]
    temp := head

    // 先判斷該鏈表是不是一個空的鏈表
    if temp.next == nil {
        fmt.Println("空空如也。。。。")
        return 
    }
    //2. 遍歷這個鏈表
    for {
        fmt.Printf("[%d , %s , %s]==>", temp.next.no, 
            temp.next.name, temp.next.nickname)
        //判斷是否鏈表後
        temp = temp.next
        if temp.next == nil {
            break
        }
    }
}


func ListHeroNode2(head *HeroNode) {

    //1. 創建一個輔助結點[跑龍套, 幫忙]
    temp := head

    // 先判斷該鏈表是不是一個空的鏈表
    if temp.next == nil {
        fmt.Println("空空如也。。。。")
        return 
    }

    //2. 讓temp定位到雙向鏈表的最後結點
    for {
        if temp.next == nil {
            break
        }
        temp = temp.next
    }


    //2. 遍歷這個鏈表
    for {
        fmt.Printf("[%d , %s , %s]==>", temp.no, 
            temp.name, temp.nickname)
        //判斷是否鏈表頭
        temp = temp.pre
        if temp.pre == nil {
            break
        }
    }
}



func main() {

    //1. 先創建一個頭結點, 
    head := &HeroNode{}

    //2. 創建一個新的HeroNode
    hero1 := &HeroNode{
        no : 1,
        name : "宋江",
        nickname : "及時雨",
    }

    hero2 := &HeroNode{
        no : 2,
        name : "盧俊義",
        nickname : "玉麒麟",
    }

    hero3 := &HeroNode{
        no : 3,
        name : "林沖",
        nickname : "豹子頭",
    }

    
    InsertHeroNode(head, hero1)
    InsertHeroNode(head, hero2)
    InsertHeroNode(head, hero3)
    ListHeroNode(head)
    fmt.Println("逆序列印")
    ListHeroNode2(head)

}
    

單向環形鏈表的應用場景

package main
import (
    "fmt"
)

//定義貓的結構體結點
type CatNode struct {
    no int //貓貓的編號
    name string
    next *CatNode
}

func InsertCatNode(head *CatNode, newCatNode *CatNode) {

    //判斷是不是添加第一隻貓
    if head.next == nil {
        head.no = newCatNode.no
        head.name = newCatNode.name
        head.next = head //構成一個環形
        fmt.Println(newCatNode, "加入到環形的鏈表")
        return 
    }
    
    //定義一個臨時變數,幫忙,找到環形的最後結點
    temp := head
    for {
        if temp.next == head {
            break
        }
        temp = temp.next
    }
    //加入到鏈表中
    temp.next = newCatNode
    newCatNode.next = head

}

//輸出這個環形的鏈表
func ListCircleLink(head *CatNode) {
    fmt.Println("環形鏈表的情況如下:")
    temp := head
    if temp.next == nil {
        fmt.Println("空空如也的環形鏈表...")
        return 
    } 
    for {
        fmt.Printf("貓的信息為=[id=%d name=%s] ->\n", temp.no, temp.name)
        if temp.next == head {
            break
        }
        temp = temp.next
    }
}

//刪除一隻貓
func DelCatNode(head *CatNode, id int) *CatNode {

    temp := head
    helper := head
    //空鏈表
    if temp.next == nil {
        fmt.Println("這是一個空的環形鏈表,不能刪除")
        return head
    }
    
    //如果只有一個結點
    if temp.next == head { //只有一個結點
        if temp.no == id {
            temp.next = nil 
        }
        return head
    }

    //將helper 定位到鏈表最後
    for {
        if helper.next == head {
            break
        } 
        helper = helper.next
    }

    //如果有兩個包含兩個以上結點
    flag := true
    for {
        if temp.next == head { //如果到這來,說明我比較到最後一個【最後一個還沒比較】
            break 
        } 
        if temp.no ==id {
            if temp == head { //說明刪除的是頭結點
                head = head.next
            }
            //恭喜找到., 我們也可以在直接刪除
            helper.next = temp.next
            fmt.Printf("貓貓=%d\n", id)
            flag = false
            break
        }
        temp = temp.next //移動 【比較】
        helper = helper.next //移動 【一旦找到要刪除的結點 helper】
    }
    //這裡還有比較一次
    if flag { //如果flag 為真,則我們上面沒有刪除
        if temp.no == id {
            helper.next = temp.next
            fmt.Printf("貓貓=%d\n", id)
        }else {
            fmt.Printf("對不起,沒有no=%d\n", id)
        }
    } 
    return head
}

func main() {

    //這裡我們初始化一個環形鏈表的頭結點
    head := &CatNode{}

    //創建一隻貓
    cat1 := &CatNode{
        no : 1,
        name : "tom",
    }
    cat2 := &CatNode{
        no : 2,
        name : "tom2",
    }
    cat3 := &CatNode{
        no : 3,
        name : "tom3",
    }
    InsertCatNode(head, cat1)
    InsertCatNode(head, cat2)
    InsertCatNode(head, cat3)
    ListCircleLink(head)

    head = DelCatNode(head, 30)

    fmt.Println()   
    fmt.Println()   
    fmt.Println()   
    ListCircleLink(head)

}

環形單向鏈表的應用實例

Josephu 問題

Josephu 問題為:設編號為 1,2,… n 的 n 個人圍坐一圈,約定編號為 k(1<=k<=n)的人從 1
開始報數,數到 m 的那個人出列,它的下一位又從 1 開始報數,數到 m 的那個人又出列,依次類推,
直到所有人出列為止,由此產生一個出隊編號的序列。

提示

用一個不帶頭結點的迴圈鏈表來處理 Josephu 問題:先構成一個有 n 個結點的單迴圈鏈表,然後
由 k 結點起從 1 開始計數,計到 m 時,對應結點從鏈表中刪除,然後再從被刪除結點的下一個結點又
從 1 開始計數,直到最後一個結點從鏈表中刪除演算法結束。
約瑟夫問題

package main
import (
    "fmt"
)


//小孩的結構體
type Boy struct {
    No int // 編號
    Next *Boy // 指向下一個小孩的指針[預設值是nil]
}

// 編寫一個函數,構成單向的環形鏈表
// num :表示小孩的個數
// *Boy : 返回該環形的鏈表的第一個小孩的指針
func AddBoy(num int) *Boy {

    first := &Boy{} //空結點
    curBoy := &Boy{} //空結點

    //判斷
    if num < 1  {
        fmt.Println("num的值不對")
        return first
    }
    //迴圈的構建這個環形鏈表
    for i := 1; i <= num; i++ {
        boy := &Boy{
            No : i,
        }
        //分析構成迴圈鏈表,需要一個輔助指針[幫忙的]
        //1. 因為第一個小孩比較特殊
        if i == 1 { //第一個小孩
            first = boy //不要動
            curBoy = boy
            curBoy.Next = first // 
        } else {
            curBoy.Next = boy
            curBoy = boy
            curBoy.Next = first //構造環形鏈表
        }
    }
    return first

}

//顯示單向的環形鏈表[遍歷]
func ShowBoy(first *Boy) {

    //處理一下如果環形鏈表為空
    if first.Next == nil {
        fmt.Println("鏈表為空,沒有小孩...")
        return
    }

    //創建一個指針,幫助遍歷.[說明至少有一個小孩]
    curBoy := first  
    for {
        fmt.Printf("小孩編號=%d ->", curBoy.No)
        //退出的條件?curBoy.Next == first
        if curBoy.Next == first {
            break
        }
        //curBoy移動到下一個
        curBoy = curBoy.Next
    }
}

/*
設編號為1,2,… n的n個人圍坐一圈,約定編號為k(1<=k<=n)
的人從1開始報數,數到m 的那個人出列,它的下一位又從1開始報數,
數到m的那個人又出列,依次類推,直到所有人出列為止,由此產生一個出隊編號的序列
*/

//分析思路
//1. 編寫一個函數,PlayGame(first *Boy, startNo int, countNum int) 
//2. 最後我們使用一個演算法,按照要求,在環形鏈表中留下最後一個人
func PlayGame(first *Boy, startNo int, countNum int) {

    //1. 空的鏈表我們單獨的處理
    if first.Next == nil {
        fmt.Println("空的鏈表,沒有小孩")
        return
    }
    //留一個,判斷 startNO <= 小孩的總數
    //2. 需要定義輔助指針,幫助我們刪除小孩
    tail := first 
    //3. 讓tail執行環形鏈表的最後一個小孩,這個非常的重要
    //因為tail 在刪除小孩時需要使用到.
    for {
        if tail.Next == first { //說明tail到了最後的小孩
            break
        }
        tail = tail.Next
    }
    //4. 讓first 移動到 startNo [後面我們刪除小孩,就以first為準]
    for i := 1; i <= startNo - 1; i++ {
        first = first.Next
        tail = tail.Next
    } 
    fmt.Println()
    //5. 開始數 countNum, 然後就刪除first 指向的小孩
    for {
        //開始數countNum-1次
        for i := 1; i <= countNum -1; i++ {
            first = first.Next
            tail = tail.Next
        }
        fmt.Printf("小孩編號為%d 出圈 \n", first.No)
        //刪除first執行的小孩
        first = first.Next
        tail.Next = first
        //判斷如果 tail == first, 圈子中只有一個小孩.
        if tail == first {
            break
        }
    }
    fmt.Printf("小孩小孩編號為%d 出圈 \n", first.No)

}

func main() {

    first := AddBoy(500)
    //顯示
    ShowBoy(first)
    PlayGame(first, 20, 31)

}

排序

排序的介紹

排序是將一組數據,依指定的順序進行排列的過程, 常見的排序:
1) 冒泡排序
2) 選擇排序
3) 插入排序
4) 快速排序

選擇排序基本介紹

選擇式排序也屬於內部排序法,是從欲排序的數據中,按指定的規則選出某一元素,經過和其他
元素重整,再依原則交換位置後達到排序的目的。

選擇排序思想:

選擇排序(select sorting)也是一種簡單的排序方法。它的基本思想是:第一次從 R[0]~R[n-1]中選
取最小值,與 R[0]交換,第二次從 R[1]~R[n-1]中選取最小值,與 R[1]交換,第三次從 R[2]~R[n-1]中選
取最小值,與 R[2]交換,…,第 i 次從 R[i-1]~R[n-1]中選取最小值,與 R[i-1]交換,…, 第 n-1 次從
R[n-2]~R[n-1]中選取最小值,與 R[n-2]交換,總共通過 n-1 次,得到一個按排序碼從小到大排列的有序
序列。

package main
import (
    "fmt"
    "math/rand"
    "time"
)

//編寫函數selectSort 完成排序

func SelectSort(arr *[80000]int) {

    //標準的訪問方式
    //(*arr)[1] = 600 等價於 arr[1] = 900
    //arr[1] = 900
    //1. 先完成將第一個最大值和 arr[0] => 先易後難

    //1 假設  arr[0] 最大值

    for j := 0; j < len(arr) - 1; j++ {

        max := arr[j]
        maxIndex := j
        //2. 遍歷後面 1---[len(arr) -1] 比較
        for i := j + 1; i < len(arr); i++ {
            if max < arr[i] { //找到真正的最大值
                max = arr[i]
                maxIndex = i
            }
        }
        //交換
        if maxIndex != j {
            arr[j], arr[maxIndex] = arr[maxIndex], arr[j]
        }

        //fmt.Printf("第%d次 %v\n  ", j+1 ,*arr)
    }
    

    /*
    max = arr[1]
    maxIndex = 1
    //2. 遍歷後面 2---[len(arr) -1] 比較
    for i := 1 + 1; i < len(arr); i++ {
        if max < arr[i] { //找到真正的最大值
            max = arr[i]
            maxIndex = i
        }
    }
    //交換
    if maxIndex != 1 {
        arr[1], arr[maxIndex] = arr[maxIndex], arr[1]
    }

    fmt.Println("第2次 ", *arr)

    

    max = arr[2]
    maxIndex = 2
    //2. 遍歷後面 3---[len(arr) -1] 比較
    for i := 2 + 1; i < len(arr); i++ {
        if max < arr[i] { //找到真正的最大值
            max = arr[i]
            maxIndex = i
        }
    }
    //交換
    if maxIndex != 2 {
        arr[2], arr[maxIndex] = arr[maxIndex], arr[2]
    }

    fmt.Println("第3次 ", *arr)

    max = arr[3]
    maxIndex = 3
    //2. 遍歷後面 4---[len(arr) -1] 比較
    for i := 3 + 1; i < len(arr); i++ {
        if max < arr[i] { //找到真正的最大值
            max = arr[i]
            maxIndex = i
        }
    }
    //交換
    if maxIndex != 3 {
        arr[3], arr[maxIndex] = arr[maxIndex], arr[3]
    }

    fmt.Println("第4次 ", *arr)*/
}

func main() {
    //定義一個數組 , 從大到小
    //arr := [6]int{10, 34, 19, 100, 80, 789}

    var arr [80000]int
    for i := 0; i < 80000; i++ {
        arr[i] = rand.Intn(900000)
    }

    //fmt.Println(arr)
    start := time.Now().Unix()
    SelectSort(&arr)
    end := time.Now().Unix()
    fmt.Printf("選擇排序耗時=%d秒", end - start)
    fmt.Println("main函數")
    //fmt.Println(arr)
}

插入排序法介紹:

插入式排序屬於 內部排序法,是對於欲排序的元素以插入的方式找尋該元素的適當位置,以達到
排序的目的。

插入排序法思想:

插入排序(Insertion Sorting)的基本思想是:把 n 個待排序的元素看成為一個有序表和一個無序表,
開始時有序表中只包含一個元素,無序表中包含有 n-1 個元素,排序過程中每次從無序表中取出第一個
元素,把它的排序碼依次與有序表元素的排序碼進行比較,將它插入到有序表中的適當位置,使之成
為新的有序表。

package main
import (
    "fmt"
    "math/rand"
    "time"
)

func InsertSort(arr *[80000]int) {

    //完成第一次,給第二個元素找到合適的位置並插入

    for i := 1; i < len(arr); i++ {

        insertVal := arr[i]
        insertIndex := i - 1 // 下標

        //從大到小
        for insertIndex >= 0 && arr[insertIndex] < insertVal {
            arr[insertIndex + 1] = arr[insertIndex] // 數據後移
            insertIndex-- 
        }
        //插入
        if insertIndex + 1 != i {
            arr[insertIndex + 1] = insertVal
        }
        //fmt.Printf("第%d次插入後 %v\n",i, *arr)
    }
    

    /*

    //完成第2次,給第3個元素找到合適的位置並插入
    insertVal = arr[2]
    insertIndex = 2 - 1 // 下標

    //從大到小
    for insertIndex >= 0 && arr[insertIndex] < insertVal {
        arr[insertIndex + 1] = arr[insertIndex] // 數據後移
        insertIndex-- 
    }
    //插入
    if insertIndex + 1 != 2 {
        arr[insertIndex + 1] = insertVal
    }
    fmt.Println("第2次插入後", *arr)

    //完成第3次,給第4個元素找到合適的位置並插入
    insertVal = arr[3]
    insertIndex = 3 - 1 // 下標

    //從大到小
    for insertIndex >= 0 && arr[insertIndex] < insertVal {
        arr[insertIndex + 1] = arr[insertIndex] // 數據後移
        insertIndex-- 
    }
    //插入
    if insertIndex + 1 != 3 {
        arr[insertIndex + 1] = insertVal
    }
    fmt.Println("第3次插入後", *arr)

    //完成第4次,給第5個元素找到合適的位置並插入
    insertVal = arr[4]
    insertIndex = 4 - 1 // 下標

    //從大到小
    for insertIndex >= 0 && arr[insertIndex] < insertVal {
        arr[insertIndex + 1] = arr[insertIndex] // 數據後移
        insertIndex-- 
    }
    //插入
    if insertIndex + 1 != 4 {
        arr[insertIndex + 1] = insertVal
    }
    fmt.Println("第4次插入後", *arr)*/
}
    

func main() {

    

    //arr := [7]int{23, 0, 12, 56,  34, -1, 55}

    var arr [80000]int
    for i := 0; i < 80000; i++ {
        arr[i] = rand.Intn(900000)
    }

    //fmt.Println(arr)
    start := time.Now().Unix()
    //fmt.Println("原始數組=", arr)
    InsertSort(&arr)
    end := time.Now().Unix()

    fmt.Println("main 函數")
    fmt.Printf("插入排序耗時%d秒", end-start)
    //fmt.Println(arr)
}

快速排序法介紹

快速排序(Quicksort)是對冒泡排序的一種改進。基本思想是:通過一趟排序將要排序的數據分
割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然後再按此方法對這
兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列

package main
import (
    "fmt"
    "math/rand"
    "time"
)

//快速排序
//說明
//1. left 表示 數組左邊的下標
//2. right 表示數組右邊的下標
//3  array 表示要排序的數組
func QuickSort(left int, right int, array *[8000000]int) {
    l := left
    r := right
    // pivot 是中軸, 支點
    pivot := array[(left + right) / 2]
    temp := 0

    //for 迴圈的目標是將比 pivot 小的數放到 左邊
    //  比 pivot 大的數放到 右邊
    for ; l < r; {
        //從  pivot 的左邊找到大於等於pivot的值
        for ; array[l] < pivot; {
            l++
        }
        //從  pivot 的右邊邊找到小於等於pivot的值
        for ; array[r] > pivot; {
            r--
        }
        // 1 >= r 表明本次分解任務完成, break
        if l >= r { 
            break
        }
        //交換
        temp = array[l]
        array[l] = array[r]
        array[r] = temp
        //優化
        if array[l]== pivot  {
            r--
        }
        if array[r]== pivot {
            l++         
        }
    }
    // 如果  1== r, 再移動下
    if l == r {
         l++
         r--
    }
    // 向左遞歸
    if left < r {
        QuickSort(left, r, array)
    }
    // 向右遞歸
    if right > l {
        QuickSort(l, right, array)
    }
}


func main() {

    // arr := [9]int {-9,78,0,23,-567,70, 123, 90, -23}
    // fmt.Println("初始", arr)

    var arr [8000000]int
    for i := 0; i < 8000000; i++ {
        arr[i] = rand.Intn(900000)
    }

    //fmt.Println(arr)
    start := time.Now().Unix()
    //調用快速排序
    QuickSort(0, len(arr) - 1, &arr)
    end := time.Now().Unix()
    fmt.Println("main..")
    fmt.Printf("快速排序法耗時%d秒", end - start)
    //fmt.Println(arr)

}

三種排序方法的速度的分析(上面代碼中已寫入)

package main
import (
    "fmt"
)

func test(n int) {
    if n > 2 {
        n-- // 死龟
        test(n)
    } else {
        fmt.Println("n=", n)
    }
    
}

func main() {

    n := 4
    test(n)
}

棧的介紹

有些程式員也把棧稱為堆棧, 即棧和堆棧是同一個概念

1) 棧的英文為(stack)
2) 棧是一個 先入後出(FILO-First In Last Out)的有序列表。
3) 棧(stack)是限制線性表中元素的插入和刪除只能線上性表的同一端進行的一種特殊線性表。允許插入和刪除的一端,為變化的一端,稱為 棧頂(Top), 另一端為固定的一端,稱為棧底(Bottom)。
4) 根據堆棧的定義可知,最先放入棧中元素在棧底,最後放入的元素在棧頂,而刪除元素剛好相
反,最後放入的元素最先刪除,最先放入的元素最後刪除

棧的應用場景

1) 子程式的調用:在跳往子程式前,會先將下個指令的地址存到堆棧中,直到子程式執行完後再
將地址取出,以回到原來的程式中。
2) 處理遞歸調用:和子程式的調用類似,只是除了儲存下一個指令的地址外,也將參數、區域變
量等數據存入堆棧中。
3) 表達式的轉換與求值。
4) 二叉樹的遍歷。
5) 圖形的深度優先(depth 一 first)搜索法

package main
import (
    "fmt"
    "errors"
)

//使用數組來模擬一個棧的使用
type Stack struct {
    MaxTop int  // 表示我們棧最大可以存放數個數
    Top int // 表示棧頂, 因為棧頂固定,因此我們直接使用Top
    arr [5]int // 數組模擬棧
}
//入棧
func (this *Stack) Push(val int) (err error) {

    //先判斷棧是否滿了
    if this.Top == this.MaxTop - 1 {
        fmt.Println("stack full")
        return errors.New("stack full")
    }
    this.Top++ 
    //放入數據
    this.arr[this.Top] = val
    return 
}

//出棧
func (this *Stack) Pop() (val int, err error) {
    //判斷棧是否空
    if this.Top == -1 {
        fmt.Println("stack empty!")
        return 0, errors.New("stack empty")
    } 

    //先取值,再 this.Top--
    val =  this.arr[this.Top]
    this.Top--
    return val, nil

}
//遍歷棧,註意需要從棧頂開始遍歷
func (this *Stack) List() {
    //先判斷棧是否為空
    if this.Top == -1 {
        fmt.Println("stack empty")
        return 
    }
    fmt.Println("棧的情況如下:")
    for i := this.Top; i >= 0; i-- {
        fmt.Printf("arr[%d]=%d\n", i, this.arr[i])
    }

}

func main() {

    stack := &Stack{
        MaxTop : 5, // 表示最多存放5個數到棧中
        Top : -1, // 當棧頂為-1,表示棧為空
    }

    //入棧
    stack.Push(1)
    stack.Push(2)
    stack.Push(3)
    stack.Push(4)
    stack.Push(5)

    //顯示
    stack.List()
    val, _ := stack.Pop()
    fmt.Println("出棧val=", val) // 5
    //顯示
    stack.List() // 

    fmt.Println()
    val, _ = stack.Pop()
    val, _ = stack.Pop()
    val, _ = stack.Pop()
    val, _ = stack.Pop()
    val, _ = stack.Pop()// 出錯
    fmt.Println("出棧val=", val) // 5
    //顯示
    stack.List() // 
}

棧實現計算表達式

package main
import (
    "fmt"
    "errors"
    "strconv"
)

//使用數組來模擬一個棧的使用
type Stack struct {
    MaxTop int  // 表示我們棧最大可以存放數個數
    Top int // 表示棧頂, 因為棧頂固定,因此我們直接使用Top
    arr [20]int // 數組模擬棧
}
//入棧
func (this *Stack) Push(val int) (err error) {

    //先判斷棧是否滿了
    if this.Top == this.MaxTop - 1 {
        fmt.Println("stack full")
        return errors.New("stack full")
    }
    this.Top++ 
    //放入數據
    this.arr[this.Top] = val
    return 
}

//出棧
func (this *Stack) Pop() (val int, err error) {
    //判斷棧是否空
    if this.Top == -1 {
        fmt.Println("stack empty!")
        return 0, errors.New("stack empty")
    } 

    //先取值,再 this.Top--
    val =  this.arr[this.Top]
    this.Top--
    return val, nil

}
//遍歷棧,註意需要從棧頂開始遍歷
func (this *Stack) List() {
    //先判斷棧是否為空
    if this.Top == -1 {
        fmt.Println("stack empty")
        return 
    }
    fmt.Println("棧的情況如下:")
    for i := this.Top; i >= 0; i-- {
        fmt.Printf("arr[%d]=%d\n", i, this.arr[i])
    }

}
//判斷一個字元是不是一個運算符[+, - , * , /]
func (this *Stack) IsOper(val int) bool {

    if val == 42 || val == 43 || val == 45 || val == 47 {
        return true
    } else {
        return false
    }
}

//運算的方法
func (this *Stack) Cal(num1 int, num2 int, oper int) int{
    res := 0
    switch oper {
        case 42 :
            res = num2 * num1
        case 43 :
            res = num2 + num1
        case 45 :
            res = num2 - num1
        case 47 :
            res = num2 / num1
        default :
            fmt.Println("運算符錯誤.")
    }
    return res
}

//編寫一個方法,返回某個運算符的優先順序[程式員定義]
//[* / => 1 + - => 0]
func (this *Stack) Priority(oper int) int {
    res := 0
    if oper == 42 || oper == 47 {
        res = 1
    } else if oper == 43 || oper == 45 {
        res = 0
    } 
    return res
} 

func main() {

    //數棧
    numStack := &Stack{
        MaxTop : 20,
        Top : -1,
    }
    //符號棧
    operStack := &Stack{
        MaxTop : 20,
        Top : -1,
    }

    exp := "30+30*6-4-6"
    //定義一個index ,幫助掃描exp
    index := 0
    //為了配合運算,我們定義需要的變數
    num1 := 0
    num2 := 0
    oper := 0
    result := 0
    keepNum := "" 

    for {
        //這裡我們需要增加一個邏輯,
        //處理多位數的問題
        ch := exp[index:index+1] // 字元串.
        //ch ==>"+" ===> 43
        temp := int([]byte(ch)[0]) // 就是字元對應的ASCiI碼
        if operStack.IsOper(temp) { // 說明是符號

            //如果operStack  是一個空棧, 直接入棧
            if operStack.Top == -1 { //空棧
                operStack.Push(temp)
            }else {
                //如果發現opertStack棧頂的運算符的優先順序大於等於當前準備入棧的運算符的優先順序
                //,就從符號棧pop出,並從數棧也pop 兩個數,進行運算,運算後的結果再重新入棧
                //到數棧, 當前符號再入符號棧
                if operStack.Priority(operStack.arr[operStack.Top]) >= 
                    operStack.Priority(temp) {
                        num1, _ = numStack.Pop()
                        num2, _ = numStack.Pop()
                        oper, _ = operStack.Pop()
                        result = operStack.Cal(num1,num2, oper)
                        //將計算結果重新入數棧
                        numStack.Push(result)
                        //當前的符號壓入符號棧
                        operStack.Push(temp)

                }else {
                    operStack.Push(temp)
                }

            }


        } else { //說明是數
            
            //處理多位數的思路
            //1.定義一個變數 keepNum string, 做拼接
            keepNum += ch 
            //2.每次要向index的後面字元測試一下,看看是不是運算符,然後處理
            //如果已經到表達最後,直接將 keepNum
            if index == len(exp) - 1 { 
                val, _ := strconv.ParseInt(keepNum, 10, 64)
                numStack.Push(int(val))
            } else {
                //向index 後面測試看看是不是運算符 [index]
                if operStack.IsOper(int([]byte(exp[index+1:index+2])[0])) {
                    val, _ := strconv.ParseInt(keepNum, 10, 64)
                    numStack.Push(int(val))
                    keepNum = ""
                }
            }
        }

        //繼續掃描
        //先判斷index是否已經掃描到計算表達式的最後
        if index + 1 == len(exp) {
            break
        }
        index++

    }

    //如果掃描表達式 完畢,依次從符號棧取出符號,然後從數棧取出兩個數,
    //運算後的結果,入數棧,直到符號棧為空
    for {
        if operStack.Top == -1 {
            break //退出條件
        }
        num1, _ = numStack.Pop()
        num2, _ = numStack.Pop()
        oper, _ = operStack.Pop()
        result = operStack.Cal(num1,num2, oper)
        //將計算結果重新入數棧
        numStack.Push(result)
        
    }

    //如果我們的演算法沒有問題,表達式也是正確的,則結果就是numStack最後數
    res, _ := numStack.Pop()
    fmt.Printf("表達式%s = %v", exp, res)
}

遞歸

遞歸的一個應用場景[迷宮問題]

遞歸的概念

簡單的說: 第歸就是函數/方法 自己調用自己,每次調用時傳入不同的變數.第歸有助於編程者解決
複雜的問題,同時可以讓代碼變得簡潔。

遞歸用於解決什麼樣的問題

1) 各種數學問題如: 8 皇後問題 , 漢諾塔, 階乘問題, 迷宮問題, 球和籃子的問題(google 編程大
賽)
2) 將用棧解決的問題-->第歸代碼比較簡潔

遞歸需要遵守的重要原則

1) 執行一個函數時,就創建一個新的受保護的 獨立空間(新函數棧)
2) 函數的局部變數是獨立的,不會相互影響, 如果希望各個函數棧使用同一個數據,使用引用傳遞
3) 遞歸必須向 退出遞歸的條件逼近【程式員自己必須分析】,否則就是無限遞歸,死龜了:)
4) 當一個函數執行完畢,或者遇到 return,就會返回,遵守誰調用,就將結果返回給誰,同時當函數執行完畢或者返回時,該函數本身也會被系統銷毀

package main
import (
    "fmt"
)

//編寫一個函數,完成老鼠找路
//myMap *[8][7]int:地圖,保證是同一個地圖,使用引用
//i,j 表示對地圖的哪個點進行測試
func SetWay(myMap *[8][7]int, i int, j int) bool {

    //分析出什麼情況下,就找到出路 
    //myMap[6][5] == 2
    if myMap[6][5] == 2 {
        return true
    } else {
        //說明要繼續找
        if myMap[i][j] == 0 { //如果這個點是可以探測

            //假設這個點是可以通, 但是需要探測 上下左右
            //換一個策略 下右上左
            myMap[i][j] = 2
            if SetWay(myMap, i + 1, j) { //下
                return true 
            } else if SetWay(myMap, i , j + 1) { //右
                return true
            } else if SetWay(myMap, i - 1, j) { //上
                return true
            } else if SetWay(myMap, i , j - 1) { //左
                return true
            } else { // 死路
                myMap[i][j] = 3
                return false
            }

        } else { // 說明這個點不能探測,為1,是強
            return false
        }

    }
}

func main() {
    //先創建一個二維數組,模擬迷宮
    //規則
    //1. 如果元素的值為1 ,就是牆
    //2. 如果元素的值為0, 是沒有走過的點
    //3. 如果元素的值為2, 是一個通路
    //4. 如果元素的值為3, 是走過的點,但是走不通
    var myMap [8][7]int

    //先把地圖的最上和最下設置為1
    for i := 0 ; i < 7 ; i++ {
        myMap[0][i] = 1
        myMap[7][i] = 1
    }

    //先把地圖的最左和最右設置為1
    for i := 0 ; i < 8 ; i++ {
        myMap[i][0] = 1
        myMap[i][6] = 1
    }

    myMap[3][1] = 1
    myMap[3][2] = 1
    // ?myMap[1][2] = 1
    // ?myMap[2][2] = 1

    //輸出地圖
    for i := 0; i < 8; i++ {
        for j := 0; j < 7; j++ {
            fmt.Print(myMap[i][j], " ")
        }
        fmt.Println()
    }

    //使用測試
    SetWay(&myMap, 1, 1)
    fmt.Println("探測完畢....")
    //輸出地圖
    for i := 0; i < 8; i++ {
        for j := 0; j < 7; j++ {
            fmt.Print(myMap[i][j], " ")
        }
        fmt.Println()
    }

}

哈希表(散列)

實際的需求

google 公司的一個上機題:

有一個公司,當有新的員工來報道時,要求將該員工的信息加入(id,性別,年齡,住址..),當輸入該員工
的 id 時,要求查找到該員工的 所有信息.
要求: 不使用資料庫,儘量節省記憶體,速度越快越好=>哈希表(散列)

哈希表的基本介紹

散列表(Hash table,也叫哈希表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也
就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做
散列函數,存放記錄的數組叫做散列表。

使用 hashtable 來實現一個雇員的管理系統[增刪改查]

應用實例 google 公司的一個上機題:

有一個公司,當有新的員工來報道時,要求將該員工的信息加入(id,性別,年齡,住址..),當輸入該員工
的 id 時,要求查找到該員工的 所有信息.

要求:

1) 不使用資料庫,儘量節省記憶體, 速度越快越好=>哈希表(散列)
2) 添加時,保證按照雇員的 id 從低到高插入
3)
思路分析

1) 使用鏈表來實現哈希表, 該 鏈表不帶表頭
[即: 鏈表的第一個結點就存放雇員信息

package main
import (
    "fmt"
    "os"
)

//定義emp
type Emp struct {
    Id int
    Name string
    Next *Emp
}
//方法待定..
func (this *Emp) ShowMe() {
    fmt.Printf("鏈表%d 找到該雇員 %d\n", this.Id % 7, this.Id)
}

//定義EmpLink
//我們這裡的EmpLink 不帶表頭,即第一個結點就存放雇員
type EmpLink struct {
    Head *Emp 
}
//方法待定..
//1. 添加員工的方法, 保證添加時,編號從小到大
func (this *EmpLink) Insert(emp *Emp) {

    cur := this.Head // 這是輔助指針
    var pre *Emp = nil // 這是一個輔助指針 pre 在cur前面
    //如果當前的EmpLink就是一個空鏈表
    if cur == nil {
        this.Head = emp //完成
        return 
    }
    //如果不是一個空鏈表,給emp找到對應的位置並插入
    //思路是 讓 cur 和 emp 比較,然後讓pre 保持在 cur 前面
    for {
        if cur != nil {
            //比較
            if cur.Id > emp.Id {
                //找到位置
                break
            }
            pre = cur //保證同步
            cur = cur.Next
        }else {
            break
        }
    } 
    //退出時,我們看下是否將emp添加到鏈表最後
    pre.Next = emp
    emp.Next = cur
    
}
//顯示鏈表的信息
func (this *EmpLink) ShowLink(no int) {
    if this.Head == nil {
        fmt.Printf("鏈表%d 為空\n", no)
        return 
    }

    //變數當前的鏈表,並顯示數據
    cur := this.Head // 輔助的指針
    for {
        if cur != nil {
            fmt.Printf("鏈表%d 雇員id=%d 名字=%s ->", no, cur.Id, cur.Name)
            cur = cur.Next
        } else {
            break
        }
    }
    fmt.Println() //換行處理
}

//根據id查找對應的雇員,如果沒有就返回nil
func (this *EmpLink) FindById(id int)  *Emp {
    cur := this.Head
    for {
        if cur != nil && cur.Id == id {
            return cur
        } else if cur == nil {
            break
        }
        cur = cur.Next
    }
    return nil
}

//定義hashtable ,含有一個鏈表數組
type HashTable struct {
    LinkArr [7]EmpLink
}

//給HashTable 編寫Insert 雇員的方法.
func (this *HashTable) Insert(emp *Emp) {
    //使用散列函數,確定將該雇員添加到哪個鏈表
    linkNo := this.HashFun(emp.Id)
    //使用對應的鏈表添加
    this.LinkArr[linkNo].Insert(emp) //
}

//編寫方法,顯示hashtable的所有雇員
func (this *HashTable) ShowAll() {
    for i := 0; i < len(this.LinkArr); i++ {
        this.LinkArr[i].ShowLink(i)
    }
}

//編寫一個散列方法
func (this *HashTable) HashFun(id int) int {
    return id % 7 //得到一個值,就是對於的鏈表的下標
}
//編寫一個方法,完成查找
func (this *HashTable) FindById(id int) *Emp {
    //使用散列函數,確定將該雇員應該在哪個鏈表
    linkNo := this.HashFun(id)
    return this.LinkArr[linkNo].FindById(id)
}


func main() {

    key := ""
    id := 0
    name := ""
    var hashtable HashTable
    for {
        fmt.Println("===============雇員系統菜單============")
        fmt.Println("input 表示添加雇員")
        fmt.Println("show  表示顯示雇員")
        fmt.Println("find  表示查找雇員")
        fmt.Println("exit  表示退出系統")
        fmt.Println("請輸入你的選擇")
        fmt.Scanln(&key)
        switch key {
            case "input":
                fmt.Println("輸入雇員id")
                fmt.Scanln(&id)
                fmt.Println("輸入雇員name")
                fmt.Scanln(&name)
                emp := &Emp{
                    Id : id,
                    Name : name,
                }
                hashtable.Insert(emp)
            case "show":
                hashtable.ShowAll()
            case "find":
                fmt.Println("請輸入id號:")
                fmt.Scanln(&id)
                emp := hashtable.FindById(id)
                if emp == nil {
                    fmt.Printf("id=%d 的雇員不存在\n", id)
                } else {
                    //編寫一個方法,顯示雇員信息
                    emp.ShowMe()
                }

            case "exit":
                os.Exit(0)
            default :
                fmt.Println("輸入錯誤")
        }
    }

}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 稀疏數組 基本介紹 當一個數組中大部分元素為0,或者為同一個值的數組時,可以使用稀疏數組來保存該數組。 如下圖所示: 稀疏數組的處理方法: 1. 記錄數組一共有幾行幾列,有多少個不同的值; 2. 把具有不同值的元素的行列及值記錄在一個小規模的數組中,從而縮小程式的規模; 稀疏數組轉換思路 二維數組轉 ...
  • 有點笨,參考了好幾篇大佬們寫的文章才整理出來的筆記.... 字面意思上解釋,線程池就是裝有線程的池,我們可以把要執行的多線程交給線程池來處理,和連接池的概念一樣,通過維護一定數量的線程池來達到多個線程的復用。 好處 多線程產生的問題 一般我們使用到多線程的編程的時候,需要通過 創建並開啟線程,我們可 ...
  • go gui 控制項和信號 控制項 控制項簡介 控制項是對數據和方法的封裝。控制項有自己的屬性和方法。屬性是指控制項的特征。方法是指控制項的一些簡單而可見的功能。如按鈕就是一個控制項,這個按鈕是方形的,裡面有張圖片,這是我們能看到外觀屬性,同時,這個按鈕具備被人按下的功能。 GTK中控制項主要分為兩類:容器控制項,非容 ...
  • struct interface 就可以實現面向對象中的繼承,封裝,多態 繼承的演示:Tsh類型繼承People類型,並且使用People類型的方法 多態的演示Tsh類型實現了介面Student,實現了介面定義的方法 完整代碼: ...
  • ```python import pymysql conn=pymysql.connect(host='localhost',user='root',password='123',db='sg',charset='utf8') #先修路-conn car = conn.cursor() #備車-ca... ...
  • 場景 使用ElementUI的快速開始的項目模板搭建Element項目後, 要在vue頁面中使用jquery的語法。 這裡直接使用$.ajax會提示$找不到。 註: 博客:https://blog.csdn.net/badao_liumang_qizhi關註公眾號霸道的程式猿獲取編程相關電子書、教程 ...
  • 封裝一個資料庫模塊有三個功能:查詢,插入,關閉 1.查看 2.提交 3.關閉 ...
  • 前言 作為一個小白,在學習之前,我非常的明確,自己要學什麼編程語言。 怎麼判斷某門編程語言掉不掉發? 說起掉發,在前言中講過,程式員很多掉發原因都是因為選“錯”了編程語言,接下來讓我們看看編程語言各個撞死人(創始人)的發量是有多麼的恐怖! PHP之父:拉斯馬斯·勒德爾夫 拉斯馬斯·勒德爾夫,創建PH ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...