算法

1、数据结构

1.1、线性表

1.1.1、链表

1.1.1.1、链表的特点

链表无需前期给它指定长度,需要的时候直接加上,地址无需像数组一样是连续的。

1.1.1.2、为什么数组的查询速度比链表快,而插入或者删除的速度比链表慢
  1. 数组的地址是连续的,当使用key来找到值的时候,直接通过映射找到对应的地址,然后直接通过地址找到对应的值,时间复杂度是o(1),而链表查询一个元素的时候,需要把链表遍历一下,如果查找的元素在最後面,那么他的时间复杂度是o(n)。
  2. 数组插入数组的时候,需要把后面的元素向后挪动,而链表插入的时候,只需要更改一下下一个元素的指向的值就可以了
  3. 数组插入数据的时候如果申请的空间不能满足现在的要求,他需要重新去申请空间,这一块也倒置链表的插入数度比数组快
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、递归算法的三个法则

  1. 首先明确关系:比如说斐波那契数列数列求第n项,其中f(n)=f(n-1)+f(n-2)
  2. 确定函数:func(n int)(n int){}
  3. 确定结束递归的条件: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]
	}
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值