各种排序算法

好烦…各种排序各种忘…
在这里插入图片描述

排序的算法比较常见的有 : 冒泡排序 快速排序 归并排序 插入排序 堆排序

冒泡排序:

基本上科班出身的秃头们,学的第一个算法就是冒泡排序吧…可能是名字比较好听,咕噜咕噜的
核心思想 :冒泡的核心思想就是,两两相比较,比较相邻的两个位置元素的大小。基本入手点就是双重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
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值