1、数据结构
1.1、线性表
1.1.1、链表
1.1.1.1、链表的特点
链表无需前期给它指定长度,需要的时候直接加上,地址无需像数组一样是连续的。
1.1.1.2、为什么数组的查询速度比链表快,而插入或者删除的速度比链表慢
- 数组的地址是连续的,当使用key来找到值的时候,直接通过映射找到对应的地址,然后直接通过地址找到对应的值,时间复杂度是o(1),而链表查询一个元素的时候,需要把链表遍历一下,如果查找的元素在最後面,那么他的时间复杂度是o(n)。
- 数组插入数组的时候,需要把后面的元素向后挪动,而链表插入的时候,只需要更改一下下一个元素的指向的值就可以了
- 数组插入数据的时候如果申请的空间不能满足现在的要求,他需要重新去申请空间,这一块也倒置链表的插入数度比数组快
1.1.1.3、链表删除或者写入的图
1.1.1.4、golang创建节点的流程
1、创建一个单节点
//链表的节点
type SingleLinkNode struct {
value interface{}
pNext *SingleLinkNode
}
2、链表的结构
//链表的结构
type SingleLinkList struct {
head *SingleLinkNode //链表的头指针
length int //链表的长度
}
3、代码参考地址
链接: 地址
1.1.2、双向链表
1.1.2.1、双向链表的特点
1、通过一个节点很容易找到他的前面节点和后面节点
2、因为他需要多一个指向前面元素的指针,所以消耗的内存比单链表
1.1.2.2、双向链表增删改差流程
1.1.2.3、双向链表创建源码
1.1.2.3.1、节点创建
package Double_Link
//双向链表节点
type DoubleLinkNode struct {
value interface{}
prev *DoubleLinkNode //上一个节点
next *DoubleLinkNode //下一个节点
}
//新建一个节点
func NewDoubleLinkNode(value interface{}) *DoubleLinkNode {
return &DoubleLinkNode{value, nil, nil}
}
//返回数据
func (node *DoubleLinkNode) Value() interface{} {
return node.value
}
//返回上一个节点
func (node *DoubleLinkNode) Prev() *DoubleLinkNode {
return node.prev
}
//返回下一个节点
func (node *DoubleLinkNode) PNext() *DoubleLinkNode {
return node.next
}
1.1.2.3.2、链表增删改差
package Double_Link
import (
"fmt"
)
//双链表的基本结构
type DoubleLinkList struct {
Head *DoubleLinkNode
length int
}
//新建一个双链表
func NewDoubleLinkList() *DoubleLinkList {
head := NewDoubleLinkNode(nil)
return &DoubleLinkList{head, 0}
}
//返回链表长度
func (dlist *DoubleLinkList) Getlength() int {
return dlist.length
}
//返回第一个节点
func (dlist *DoubleLinkList) GetFirstNode() *DoubleLinkNode {
return dlist.Head.next
}
//头部插入
func (dlist *DoubleLinkList) InsertHead(node *DoubleLinkNode) {
pHead := dlist.Head //头部指针
if pHead.next == nil {
pHead.next = node
node.next = nil
node.prev = pHead
} else {
pHead.next.prev = node
node.next = pHead.next
node.prev = pHead
pHead.next = node
}
}
//尾部插入
func (dlist *DoubleLinkList) InsertBack(node *DoubleLinkNode) {
pHead := dlist.Head //头部指针
if pHead.next == nil {
node.prev = pHead
node.next = nil
pHead.next = node
} else {
for pHead.next != nil {
pHead = pHead.next
}
node.prev = pHead
node.next = nil
pHead.next = node
}
}
//插入一个节点之前
func (dlist *DoubleLinkList) InsertNodeBack(node *DoubleLinkNode, dest *DoubleLinkNode) bool {
pHead := dlist.Head //头部指针
if pHead == nil {
return false
}
for pHead.next != nil && pHead.next != dest {
pHead = pHead.next
}
if pHead.next == nil {
return false
}
node.next = pHead.next
node.prev = pHead
dest.prev = node
pHead.next = node
return true
}
//插入一个节点之后
func (dlist *DoubleLinkList) InsertNodeHead(node *DoubleLinkNode, dest *DoubleLinkNode) bool {
pHead := dlist.Head //头部指针
if pHead == nil {
return false
}
for pHead.next != nil && pHead.next != dest {
pHead = pHead.next
}
if pHead.next == nil {
return false
}
pnext := pHead.next.next
pHead.next.next = node
node.next = pnext
node.prev = pHead.next
pHead.next.next.next.prev = node
return true
}
//遍历
func (dlist *DoubleLinkList) String() string {
var listString1 string
var listString2 string
phead := dlist.Head
for phead.next != nil {
//正向循环
listString1 += fmt.Sprintf("%v-->", phead.next.value)
phead = phead.next
}
listString1 += fmt.Sprintf("nil")
listString1 += "\n"
for phead != dlist.Head {
//反向循环
listString2 += fmt.Sprintf("<--%v", phead.value)
phead = phead.prev
}
listString1 += fmt.Sprintf("nil")
return listString1 + listString2 + "\n" //打印链表字符串
}
//删除一个节点
//遍历
func (dlist *DoubleLinkList) Delete(value interface{}) bool {
pHead := dlist.Head //指针
if pHead.next == nil {
return false
}
for pHead.next != nil && pHead.next.value != value {
pHead = pHead.next
}
if pHead.next == nil {
return false
}
pHead.next.prev.next = pHead.next.next
// pHead.next.next.prev = pHead.next.prev
return true
}
1.1.2.3.3、链表的调用
package main
import (
"fmt"
"test/Double_Link"
)
func main() {
list := Double_Link.NewDoubleLinkList()
node1 := Double_Link.NewDoubleLinkNode(1)
list.InsertHead(node1)
node2 := Double_Link.NewDoubleLinkNode(2)
list.InsertHead(node2)
node3 := Double_Link.NewDoubleLinkNode(3)
list.InsertHead(node3)
node4 := Double_Link.NewDoubleLinkNode(4)
list.InsertBack(node4)
node5 := Double_Link.NewDoubleLinkNode(5)
list.InsertBack(node5)
node6 := Double_Link.NewDoubleLinkNode(6)
list.InsertBack(node6)
node7 := Double_Link.NewDoubleLinkNode(7)
list.InsertNodeBack(node7, node2)
node8 := Double_Link.NewDoubleLinkNode(8)
list.InsertNodeBack(node8, node2)
fmt.Println(list.String())
node9 := Double_Link.NewDoubleLinkNode(9)
list.InsertNodeHead(node9, node2)
node10 := Double_Link.NewDoubleLinkNode(10)
list.InsertNodeHead(node10, node2)
list.Delete(8)
fmt.Println(list.String())
}
1.1.2.3.4、执行结果
1.1.3、环形链表解决约瑟夫问题
package main
import "fmt"
type Node struct {
value int
next *Node
}
type list struct {
Head *Node
Tail *Node
}
func (l *list) push(node *Node) { //从后面插入环形链表
phead := l.Head
if phead == nil { //如果第一个节点为空,则新添加的节点及即是首节点,也是尾节点
l.Tail = node
l.Head = node
l.Head.next = l.Tail
l.Tail.next = l.Head
} else { //如果首节点不是空
// fmt.Println()
l.Tail.next = node //新添加的节点加入尾部
node.next = l.Head //此时的尾部就是node,node的next就是head
l.Tail = node //此时的tail就是node
}
}
func (l *list) showList() {
phead := l.Head //找出头部
if phead == nil {
return
}
ptail := l.Tail //找出尾部
for phead != ptail { //循环
// fmt.Println(phead.value)
phead = phead.next //移动头部指针
}
// fmt.Println(phead.value)
}
func (l *list) JosePh(skip, num int) { //从第skip个开始,每次递num下
//因为是从skip开始的,所以需要调整一下头节点和尾节点
phead := l.Head
ptail := l.Tail
for i := 0; i < skip; i++ {
phead = phead.next
ptail = ptail.next
}
//开始进行传递
count := 1
for {
count++ //开始记录次数
phead = phead.next
ptail = ptail.next //循环到起点
if count == num {
fmt.Println(phead.value, "出局")
ptail.next = phead.next //删除一个
phead = phead.next
count = 1 //清零
}
if phead == ptail { //相等意味着仅剩一个
fmt.Println(phead.value, "最后一个")
break
}
}
}
func main() {
list := list{}
for i := 0; i < 10; i++ {
node := Node{i, nil}
list.push(&node)
}
list.showList()
list.JosePh(3, 3)
}
1.2、树形结构
1.3、图形结构
2、递归
2.1、为什么要学习递归
递归贯穿整个算法思想,如果无法理解递归,就永远也学不好算法,就我总结的经验来看,咱们人不能像机器一样一层一层往里面套,得要从大局观考虑。
2.2、递归算法的三个法则
- 首先明确关系:比如说斐波那契数列数列求第n项,其中f(n)=f(n-1)+f(n-2)
- 确定函数:func(n int)(n int){}
- 确定结束递归的条件:n <=2
2.3、例子
2.3.1、斐波那契数列数列求第n项
2.3.1.1、确定规则
f(n)=f(n-1)+f(n-2)
2.3.1.2、确定函数
func f(n int)(total int){
return f(n-1)+f(n-2)
}
2.3.1.2、确定调出递归的条件
func f(n int)(total int){
if n <= 2{
return 1
}
return f(n-1)+f(n-2)
}
2.3.2、一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
2.3.2.1、确定规则
因为只能一次跳一级台阶或者二级台阶,所以跳到n阶台阶的方法是完成n-1阶的方法+完成n-2阶的方法,所以可以确定关系
f(n) = f(n-1)+f(n-2)
2.3.2.2、确定方法
func f(n int)(total int){
f(n) = f(n-1)+f(n-2)
}
2.3.2.4、确定退出递归条件
func f(n int)(total int){
if n == 0 {
return 0
}
if n = =1 {
return 1
}
if n == 2{
return 2
}
f(n) = f(n-1)+f(n-2)
}
3、排序算法
3.1、选择排序
3.1.1、选择排序的方法
在未排序的切片中选取最小的值放在首位,然后在未排序的切片中选取最小的值放在第二位,以此类推。
3.1.2、示例代码
func main() {
arr := []int{6, 2, 4, 900, 100, 1111, 8, 9, 1, 4, 0, 10, 5, 2}
selectionSort(arr, 0)
fmt.Println(arr)
}
func selectionSort(arr []int, start int) {
if start == len(arr) {
return
}
minIdx := start
minVal := arr[start]
for i := start + 1; i < len(arr); i++ {
if arr[i] < minVal {
minIdx, minVal = i, arr[i]
}
}
arr[start], arr[minIdx] = arr[minIdx], arr[start]
selectionSort(arr, start+1)
}
3.1.3、时间复杂度分析
复杂度分析: 选择排序的交换操作介于 0和 (n-1)次之间。选择排序的比较操作为n(n-1)/2次。选择排序的赋值操作介于 0和 3(n-1)次之间。
比较次数 O(n^2),比较次数与关键字的初始状态无关,总的比较次数 N=(n-1)+(n-2)+…+1=n (n-1)/2。交换次数 O(n),最好情况是,已经有序,交换0次;最坏情况是,逆序,交换n-1次。交换次数比冒泡排序较少,由于交换所需CPU时间比比较所需的CPU时间多,n值较小时,选择排序比冒泡排序快。
原地操作几乎是选择排序的唯一优点,当空间复杂度要求较高时,可以考虑选择排序;实际适用的场合非常罕见。
3.2、冒泡排序
3.2.1、冒泡排序的规则
1、第一次循环先把当前数组里面的最大或者最小的元素放在最后一个位置,寻找最大或者最小的方式是从下标为0开始,依次从左往右两两进行比较,如果满足条件则调换位置。这样就能把最大或者最小放到最右边
2、第二轮是找到第二大或者大二小的,放到右边的第二的位置,依次类推,比较n-1轮就行,从我代码也看到,为什么内层循环的条件为len(arr)-j-1,是因为没一轮循环,就会排序好一个数,所以排序好的就没必要在排序了。
总之总结下来是:内存循环是把一个元素排序好,外层循环是有多少个元素就循环多少次。
3.2.2、代码示例
package main
import "fmt"
/***
冒泡排序
2, 3, 2, 4, 5, 7, 2, 5,8
*/
func main() {
arr := []int{8, 7, 6, 5, 4, 3, 2, 1}
bubbleSort(arr)
fmt.Println(arr)
}
func bubbleSort(arr []int) {
for j := 0; j < len(arr); j++ {//把所有的元素都经过一次排序
for i := 0; i < len(arr)-1-j; i++ { //把一个元素排序好
if arr[i+1] < arr[i] {
arr[i], arr[i+1] = arr[i+1], arr[i]
}
}
}
}
3.3、选择排序
3.3.1、选择排序的规则
它的基本思想是:第一次从arr[0]arr[n-1]中选取最小值,与arr[0]交换,第二次从arr[1]arr[n-1]中选取最小值,与arr[1]交换,第三次从arr[2]arr[n-1]中选取最小值,与arr[2]交换,…,第i次从arr[i-1]arr[n-1]中选取最小值,与arr[i-1]交换,…, 第n-1次从arr[n-2]~arr[n-1]中选取最小值,与arr[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。
3.3.2、代码示例
package main
import "fmt"
/***
冒泡排序
2, 3, 2, 4, 5, 7, 2, 5,8
*/
func main() {
arr := []int{8, 101, 6, 100, 4, 3, 19, 1}
SellectSort(arr)
fmt.Println(arr)
}
func SellectSort(arr []int) {
for j := 0; j < len(arr); j++ {
MinIdx := j
MinVal := arr[j]
for i := j + 1; i < len(arr); i++ {
if arr[i] < MinVal {
MinIdx, MinVal = i, arr[i]
}
}
arr[j], arr[MinIdx] = MinVal, arr[j]
}
}