go-数据结构
数据结构
数据结构(算法)的介绍
数据结构的介绍
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("输入错误") } } }