好烦……各种排序各种忘…..
排序的算法比较常见的有: 冒泡排序 快速排序 归并排序 插入排序 堆排序
�
冒泡排序:
基本上科班出身的秃头们,学的第一个算法就是冒泡排序吧…..可能是名字比较好听,咕噜咕噜的
核心思想:冒泡的核心思想就是,两两相比较,比较相邻的两个位置元素的大小。基本入手点就是双重for循环
但是经常我们会搞错了冒泡,看下面的代码
func test(nums []int) (ret []int) {
swap := func(nums []int,i,j int) []int {
num := nums[i]
nums[i] = nums[j]
nums[j] = num
return 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] = num
return nums
}
flag := true
for i := 0; i < len(nums) && flag; i++ {
flag = false
for 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 := left
left++
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 := left
left++
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+1
for 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 := 0
for 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) / 2
sort(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+1
for 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 := 0
for 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 int
for 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)/2
end := len(newNums) -1
for 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
}