1) 數據結構是一門研究算法的學科,只從有了編程語言也就有了數據結構.學好數據結構能夠編寫
出更加漂亮,更加有效率的代碼。
2) 要學習好數據結構就要多多考慮如何將生活中遇到的問題,用程序去實現解決.
3) 程序 = 數據結構 + 算法
20.2 數據結構和算法的關係node
爲何有些網站可以在高併發,和海量吞吐狀況下依然堅如磐石,你們可能會
說: 網站使用了服務器羣集技術、數據庫讀寫分離和緩存技術(好比 Redis 等),那若是我再深刻的問
一句,這些優化技術又是怎樣被那些天才的技術高手設計出來的呢?
你們請思考一個問題,是什麼讓不一樣的人寫出的代碼從功能看是同樣的,但從效率上卻有天壤之別程序員
曾經我聽有人是這麼說的:" 我是作服務器的,環境是 UNIX,功能是要支持上千萬人同時在線,
並保證數據傳輸的穩定, 在服務器上線前,作內測,一切 OK,可上線後,服務器就支撐不住了, 公
司的 CTO 對個人代碼進行優化,再次上線,堅如磐石。那一瞬間,我認識到程序是有靈魂的,就是
算法。拿在公司工做的實際經從來說,若是你不想永遠都是代碼工人,那就花時間來研究下算法吧!"算法
先看一個實際的需求數據庫
當一個數組中大部分元素爲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() } }
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)而直接進行訪問的數據結構。也
就是說,它經過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫作
散列函數,存放記錄的數組叫作散列表。
應用實例 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("輸入錯誤") } } }