一、快速排序算法
快速排序(Quicksort)是对冒泡排序算法的一种改进。
快速排序使用分治法(Divide and conquer)策略来把一个序列(数组)分为两个子序列(数组)。
算法步骤:
1、 首先设定一个分界值,通过该分界值将数组分成左右两部分
2 、大于或等于分界值的数据集中到数组右边(相同的数可以到任意一边),小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
在这个分区退出之后,该分界值就处于数列的中间位置,这个称为分区(partition)操作。
3 、左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
4、重复上述过程,通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了
递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
Go代码常规实现:
func Quick2Sort(values []int) {
if len(values) <= 1 {
return
}
mid, i := values[0], 1
head, tail := 0, len(values)-1
for head < tail {
fmt.Println(values)
if values[i] > mid {
values[i], values[tail] = values[tail], values[i]
tail--
} else {
values[i], values[head] = values[head], values[i]
head++
i++
}
}
values[head] = mid
Quick2Sort(values[:head])
Quick2Sort(values[head+1:])
}
Go代码粗糙实现:
func main() {
m := []int{1, 45, 8, 90, 3, 9, 98, 45, 41, 18, 45, 888, 58}
mid := 0 //定义初始基准元素下标
n := Sort(m, mid)
fmt.Println(n) // [1 3 8 9 18 41 45 45 45 58 90 98 888]
}
func Sort(a []int, mid int) []int {
if len(a) <= 1 {
return a
}
if mid > len(a)-1 { //基准越界时,从首元素开始
mid = 0
}
left := []int{} // 定义存放小于基准元素的元素的切片
right := []int{} //定义大于存在大于或者等于基准元素的元素的切片
c := a[mid]
IsEqual := true //定一个相同元素的对比标志位
for i := 0; i < len(a); i++ {
b := a[i]
if b < c {
left = append(left, b)
} else if b > c {
right = append(right, b)
} else {
// 当遇到相同元素时,为了不出现相同元素一边倒,导致递归死循环,
// 用个标志位,使左右两边至少有一个相同元素,
// 每次执行完,相同元素至少减1,不会出现死循环
if IsEqual {
left = append(left, b)
} else {
right = append(right, b)
}
IsEqual = false
}
}
mid++ //防止死循环,基准元素像后移一位
x := Sort(left, mid)
y := Sort(right, mid)
x = append(x, y...)
return x
}
二、归并排序算法(Merge Sort)
是建立在归并操作(归并操作,也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法。)上的一种有效,稳定的排序算法。
该算法是采用分治法的一个非常典型的应用。
速度仅次于快速排序;
归并算法步骤:
1. 定义合并序列,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4. 重复步骤3直到某一指针达到序列尾
5. 将另一序列剩下的所有元素直接复制到合并序列尾。
归并排序算法具体代码实现:
1:将长度为N的序列一分为二为两个子序列,然后将两个子序列再一分为二,然后这样递归操作,将序列分拆分为若干个长度为1的子序列。
2、将序列每相邻两个数字进行归并算法操作(merge),形成floor(n/2+n%2)个序列,排序后每个序列包含两个元素,将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素。
3、重复步骤2(归并算法操作),直到所有元素排序完毕。
Go语言实现归并算法
func mergeSort(r []int) []int {
length := len(r)
if length <= 1 {
return r
}
num := length / 2
left := mergeSort(r[:num])
right := mergeSort(r[num:])
return merge(left, right)
}
func merge(left, right []int) (result []int) {
l, r := 0, 0
for l < len(left) && r < len(right) {
if left[l] < right[r] {
result = append(result, left[l])
l++
} else {
result = append(result, right[r])
r++
}
}
result = append(result, left[l:]...)
result = append(result, right[r:]...)
return
}
三、二分查找算法
二分查找算法是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜 素过程结束;
如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组 为空,则代表找不到。
这种搜索算法每一次比较都使搜索范围缩小一半。折半搜索每次把搜索区域减少一半,时间复杂度为Ο(logn) 。
要求:
1.必须采用顺序存储结构。
2.必须按关键字大小有序排列。
四:冒泡排序算法
冒泡排序算法也称为下沉排序。在这种类型的排序中,要排序的列表的相邻元素之间互相比较。如果它们按顺序排列错误,将交换值并以正确的顺序排列,直到最终结果“浮”出水面。
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法
Go代码实现法之一:
func main() {
var tmp int
number := []int{95, 45, 15, 78, 84, 51, 24, 12}
for i := 0; i < LENGTH; i++ {
for j := LENGTH - 1; j > i; j-- {
if number[j] < number[j-1] {
tmp = number[j-1]
number[j-1] = number[j]
number[j] = tmp
}
}
}
for i := 0; i < LENGTH; i++ {
fmt.Printf("%d ", number[i])
}
fmt.Printf("\n")
}
五:堆排序算法
是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构(,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
(如果一颗二叉树最多只有最下面的两层结点度数可以小于2,并且最下面一层的结点都集中在该层最左边的连续位置上,则此二叉树称做完全二叉树(complete binary tree))
比如:
利用最小堆的特性,我们每次都从堆顶弹出一个元素(这个元素就是当前堆中的最小值),即可实现升序排序。
https://www.cnblogs.com/Mishell/p/14131009.html
https://www.cnblogs.com/yahuian/p/11945144.html
https://blog.csdn.net/weixin_43644373/article/details/103089678
六:DFS(深度优先搜索)
深度优先搜索算法(Depth-First-Search)属于图算法的一种,是一种在开发爬虫早期使用较多的方法,DFS属于盲目搜索。
其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。
深度优先遍历图算法步骤:
1. 访问顶点v;
2. 依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;
3. 若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。
上述描述可能比较抽象,举个实例:
DFS 在访问图中某一起始顶点 v 后,由 v 出发,访问它的任一邻接顶点 w1;再从 w1 出发,访问与 w1邻 接但还没有访问过的顶点 w2;然后再从 w2 出发,进行类似的访问,… 如此进行下去,直至到达所有的邻接顶点都被访问过的顶点 u 为止。
接着,退回一步,退到前一次刚访问过的顶点,看是否还有其它没有被访问的邻接顶点。如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问;如果没有,就再退回一步进行搜索。重复上述过程,直到连通图中所有顶点都被访问过为止。
当然,当人们刚刚掌握深度优先搜索的时候常常用它来走迷宫。
深搜,顾名思义,是深入其中、直取结果的一种搜索方法,如果深搜是一个人,那么他的性格一定倔得像头牛!他从一点出发去旅游,只朝着一个方向走,除非路断了,他绝不改变方向!除非四个方向全都不通或遇到终点,他绝不后退一步!
深搜优缺点
优点
1、能找出所有解决方案
2、优先搜索一棵子树,然后是另一棵,所以和广搜对比,有着内存需要相对较少的优点
缺点
1、要多次遍历,搜索所有可能路径,标识做了之后还要取消。
2、在深度很大的情况下效率不高
七:BFS(广度优先搜索)
广度优先搜索算法(Breadth-First-Search),是一种图形搜索算法,
如果广搜是一个人,那么她一定很贪心,而且喜新厌旧!她从一点出发去旅游,先把与起点相邻的地方全部游览一遍,然后再把与她刚游览过的景点相邻的景点全都游览一边……一直这样,直至所有的景点都游览一遍。
广搜属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。类似树的按层遍历,其过程为:首先访问初始点Vi,并将其标记为已访问过,接着访问Vi的所有未被访问过可到达的邻接点Vi1、Vi2…Vit,并均标记为已访问过,然后再按照Vi1、Vi2…Vit 的次序,访问每一个顶点的所有未被访问过的邻接点,并均标记为已访问过,依此类推,直到图中所有和初始点Vi有路径相通的顶点都被访问过为止。
广搜优缺点
优点
1、对于解决最短或最少问题特别有效,而且寻找深度小。
2、每个结点只访问一遍,结点总是以最短路径被访问,所以第二次路径确定不会比第一次短
缺点。
1、内存耗费量大(需要开大量的数组单元用来存储状态)。
八:动态规划算法
动态规划(Dynamic programming)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。
动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多 子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个 子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。
关于动态规划最经典的问题当属背包问题。
算法步骤:
1. 最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
2. 子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。 动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是 在表格中简单地查看一下结果,从而获得较高的效率。