好烦……各种排序各种忘…..
排序的算法比较常见的有: 冒泡排序 快速排序 归并排序 插入排序 堆排序
�
冒泡排序:
基本上科班出身的秃头们,学的第一个算法就是冒泡排序吧…..可能是名字比较好听,咕噜咕噜的
核心思想:冒泡的核心思想就是,两两相比较,比较相邻的两个位置元素的大小。基本入手点就是双重for循环
但是经常我们会搞错了冒泡,看下面的代码
func test(nums []int) (ret []int) {swap := func(nums []int,i,j int) []int {num := nums[i]nums[i] = nums[j]nums[j] = numreturn nums}for i := 0; i < len(nums); i++ {for j := i+1; j < len(nums); j++ {if nums[i] > nums[j] {nums = swap(nums,i,j)}}}return nums}
这个是你所想的冒泡嘛? 错!!!大错特错了….冒泡排序的核心思想是相邻的两个位置相比较嘛,所以是这样的
func maopao(nums []int) (ret []int) {swap := func(nums []int,i,j int) []int {num := nums[i]nums[i] = nums[j]nums[j] = numreturn nums}flag := truefor i := 0; i < len(nums) && flag; i++ {flag = falsefor j := 0; j < len(nums)-i-1; j++ {if nums[j] > nums[j+1] {nums = swap(nums,j,j+1)flag = true}}}return nums}
快速排序:
快排一般是有关面试算法的必问题….唉…..
核心思想:基准数 从数组中选择一个基准数(一般是最开头的内个元素)让其他比基准数小的元素移动到一边,大的移动另一边。 在对左右两边的区间重复上一次操作,直到区间中剩下一个元素
快速排序和归并排序 其实用到的都是分治法的思想,但是却有所不同。归并排序是自下而上的,先处理子问题,然后再合并,将小集合合成大集合,最后实现排序。而快速排序是由上到下的,先分区,然后再处理子问题。
下面代码实现
(递归版)
func kuaipai(nums []int) []int {//比较置换swap := func(nums []int,i,j int) {num := nums[i]nums[i] = nums[j]nums[j] = num}//选出基准数,进行分区partition := func(nums []int,left,right int) int{start := leftleft++for left < right {for left < right && nums[left] < nums[start] {left++}for left < right && nums[right] > nums[start] {right--}if left >= right {break}swap(nums,left,right)}//如果当前值比基准元素还大,那将基本元素和当前值的前一位置换if nums[right] > nums[start] {right--}swap(nums,start,right)return right}var sort func(nums []int,left,right int)sort = func(nums []int,left,right int) {if left < right {par := partition(nums, left, right)sort(nums,left,par-1)sort(nums,par+1,right)}}sort(nums,0,len(nums)-1)return nums}
一般升级版算法题,不让你用递归….所以需要用“栈” 利用栈的先进后出的特点, 找寻基准数,并分区的方法不变,将原来的递归改成压栈
(非递归版)
func kuaipaiFDG(nums []int) []int {//比较置换swap := func(nums []int,i,j int) {num := nums[i]nums[i] = nums[j]nums[j] = num}//选出基准数,进行分区partition := func(nums []int,left,right int) int{start := leftleft++for left < right {for left < right && nums[left] < nums[start] {left++}for left < right && nums[right] > nums[start] {right--}if left >= right {break}swap(nums,left,right)}//如果当前值比基准元素还大,那将基本元素和当前值的前一位置换if nums[right] > nums[start] {right--}swap(nums,start,right)return right}//使用栈,栈里存储这每一次基准数的分区区间 每一次弹栈出来一个区间,在继续寻找基准数,区分分区...以此类推,直到每一个分区都是一个元素var stack []int//先把起始和结束放进去stack = append(stack,0)stack = append(stack,len(nums)-1)for len(stack) != 0 {right := stack[len(stack)-1]stack = stack[:len(stack)-1]left := stack[len(stack)-1]stack = stack[:len(stack)-1]if left < right {par := partition(nums,left,right)//压栈stack = append(stack,left)stack = append(stack,par-1)stack = append(stack,par+1)stack = append(stack,right)}}return nums}
快排是可以优化的…我们在寻找基准数的时候,都是默认区间内的第一个.这样在极端的情况下,效率很低,可以用三数取中法啥的…这个自己看看吧,看太多也都记不住…
归并排序
归并排序也是必须掌握的排序算法….上面也说了它和快排都是采用分治法,但是又略有不用
核心思想:分治法, 不同于快排的是 它是先处理子问题,在合并成分区..也就是说把一个长度为N的数组,分割成N个子序列,长度为1 。之后两两归并成一个排好序的区间,这样最后归并成一个区间,
放一个图吧..网上找的,就更好了理解了
下面代码实现
(递归版)
func guibingDG(nums []int) []int {merge := func(nums []int, left,right,mid int) {//首选,创建一个存储归并后的大集合数组var mergeNums []int//其次,用两个指针,分别在两个区间内比较,归并两个区间tmp,tmp1 := left,mid+1for tmp <= mid || tmp1 <= right {if tmp > mid {mergeNums = append(mergeNums,nums[tmp1])tmp1++continue}if tmp1 > right {mergeNums = append(mergeNums,nums[tmp])tmp++continue}//俩俩比较落入新的集合数组if nums[tmp] <= nums[tmp1] {mergeNums = append(mergeNums,nums[tmp])tmp++}else {mergeNums = append(mergeNums,nums[tmp1])tmp1++}}//最后,将大集合 还原到原先数组中index := 0for i := left; i <= right && len(mergeNums) > 0; i++ {nums[i] = mergeNums[index]index++}}var sort func(nums []int, left,right int)sort = func(nums []int, left,right int) {if left < right {mid := left + (right-left) / 2sort(nums,left,mid)sort(nums,mid+1,right)merge(nums,left,right,mid)}}sort(nums,0,len(nums)-1)return nums}
(非递归版)merge归并方法都是一样的,只不过非递归版本是用变量标志子集的长度大小
func guibingFDG(nums []int) []int {merge := func(nums []int, left,right,mid int) {//首选,创建一个存储归并后的大集合数组var mergeNums []int//其次,用两个指针,分别在两个区间内比较,归并两个区间tmp,tmp1 := left,mid+1for tmp <= mid || tmp1 <= right {if tmp > mid {mergeNums = append(mergeNums,nums[tmp1])tmp1++continue}if tmp1 > right {mergeNums = append(mergeNums,nums[tmp])tmp++continue}//俩俩比较落入新的集合数组if nums[tmp] <= nums[tmp1] {mergeNums = append(mergeNums,nums[tmp])tmp++}else {mergeNums = append(mergeNums,nums[tmp1])tmp1++}}//最后,将大集合 还原到原先数组中index := 0for i := left; i <= right && len(mergeNums) > 0; i++ {nums[i] = mergeNums[index]index++}}k := 1 //子集合大小 1,2,4,8....for k < len(nums) {var i intfor i = 0; i < len(nums)-2*k; i += 2*k {merge(nums,i,i+2*k-1,i+k-1)}if i+k < len(nums) {merge(nums,i,len(nums)-1,i+k-1)}k = 2*k}return nums}
插入排序:
插入排序也算是基础算法中的一种了..感觉不是咋常用….
核心思想: 将一个元素插入到已经排好序的序列中,得到一个新的序列。 也就是说我们要将原序列分成两个区间,有序区间和无序区间,每次从无序区间中取一个,插入到有序区间
直接上代码吧..
func charu(nums []int) []int {//把第一个元素当做是有序区间 , 所以i=1 从1开始for i := 1; i < len(nums); i++ {tmp := nums[i]var j int//有序区间从后往前,和元素比较并移动位置,为元素腾地方for j = i - 1; j >= 0; j-- {if tmp < nums[j] {nums[j+1] = nums[j]continue}break}//归位nums[j+1] = tmp}return nums}
堆排序:
分析堆排序之前,需要知道啥玩应是堆….堆的使用场景还是非常之多的
堆的定义:
- 必须是完全二叉树。(啥是完全二叉树?…自己百度吧..)
- 二叉堆中的每个节点,都必须 大于等于 或者 小于等于 其子树中的每一个节点的值(顶点是最大值是大顶堆,反之是小顶堆)
因为二叉堆是完全二叉树,所以我们完全可以用数组来存储二叉堆,看图..
这样我们能准确的找到,每一个父节点(i) 对应的左子节点(2i) 右子节点(2i+1)的坐标…..
关键问题:如何进行堆排序?
主要分为两步骤:
- 建堆
- 排序
建堆的方法主要有:上浮建堆 和 下沉建堆
拿上浮建堆来说,我们每个节点的坐标是已经知道了,那我们在数组的末端要添加一个元素的话,是不是要看这个元素满足小顶堆(或大顶堆),将添加的这个元素与其父节点相比较,如果小于父节点就和其置换位置..这样一点一点该元素就上浮到自己应该在的位置
堆排序具体的执行逻辑
1.建堆,通过下沉操作建堆效率更高,具体过程是,找到最后一个非叶子节点,然后从后往前遍历执行下沉操作。
2.排序,将堆顶元素(代表最大元素)与最后一个元素交换,然后新的堆顶元素进行下沉操作,遍历执行上诉操作,则可以完成排序。
代码如下:
func heap(nums []int) []int {//比较置换swap := func(nums []int,i,j int) {num := nums[i]nums[i] = nums[j]nums[j] = num}//下沉建堆sink := func(nums []int, root,end int) {//如果该root没有左子节点了,说明下沉到位了for 2*root <= end {left := 2*root //左子节点的坐标right := left+1 //右子节点的坐标index := left//找出root的左右两个子节点的最大值(大顶堆),之后再和当前root值比较if right <= end && nums[right] > nums[left]{index = right}if nums[index] > nums[root] {swap(nums,root,index)}else {break}root = index}}//排序 先创建一个新数组,起始位是空余节点newNums := []int{0}for i := 0; i < len(nums); i++ {newNums = append(newNums,nums[i])}//先下沉建堆,将数组变成一个大顶堆,找出所有可能的顶点..最小的是 len(nums)/2end := len(newNums) -1for i := len(nums)/2; i >= 1; i-- {sink(newNums,i,end)}for end > 1 {swap(newNums,1,end)end--sink(newNums,1,end)}for i := 1; i < len(newNums); i++ {nums[i-1] = newNums[i]}return nums}
