go语言数据结构与排序算法

Go 复制代码
package main

import "fmt"

func main() {
	Bubble_Sort()
	Select_Sort()
	Insert_Sort()
	Shell_Sort()
    Heap_Sort()
    Merge_Sort()
    Quick_Sort()
}

一、

1、冒泡排序

冒泡排序的基本思想是:

  1. 从左到右不断交换相邻逆序的元素
  2. 一轮下来,最大元素会交换移到右端
  3. 对除最后元素外的子序列,重复步骤 1
  4. 若没有发生交换,说明序列已排序完成

内层循环处理交换,外层循环控制轮数。

Go 复制代码
// 冒泡排序
func Bubble_Sort() {
	str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
	// 反向冒泡
	for i := 0; i < len(str)-1; i++ {
		for j := 1; j <= len(str)-i; j++ {
			if str[j-1] > str[j] {
				str[j-1], str[j] = str[j], str[j-1]
			}
		}
	}

	// 正向冒泡
	for i := 0; i < len(str)-1; i++ {
		for j := len(str) - 1; j > i; j-- {
			if str[j-1] > str[j] {
				str[j-1], str[j] = str[j], str[j-1]
			}
		}
	}

	//改进
    //如果在某一轮中没有发生交换,说明数组已经有序,可以提前退出循环。
	for i := 0; i < len(str)-1; i++ {
		flag := false
		for j := 1; j <= len(str)-i; j++ {
			if str[j-1] > str[j] {
				str[j-1], str[j] = str[j], str[j-1]
				flag = true
			}
		}
		if !flag {
			break
		}
	}
	fmt.Println(str)
}

2、快速排序

其核心思想是:

  1. 选择一个基准值。
  2. 将数组分为两部分,左边小于基准值,右边大于或等于基准值。
  3. 对左右两部分递归排序,最终合并。
Go 复制代码
func Quick_Sort() {
	str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
	Quicking_Sort(str, 0, 8)
	fmt.Println(str)
}
func Quicking_Sort(str []int, low, high int) {
	if low < high {
		pivot := Partition_1(str, low, high)
		Quicking_Sort(str, low, pivot-1)
		Quicking_Sort(str, pivot+1, high)
	}
}

func Partition_0(str []int, low, high int) int {
	//选取基准点。也可以从三个数或者九个数中选取大小为中间值的数作为基准
	pivotkey := str[low]

	for low < high { //从表的两端交替向中间扫描
		for low < high && str[high] > pivotkey {
			high--
		}
		//1、交换法
		//str[low], str[high] = str[high], str[low]

		for low < high && str[low] < pivotkey {
			low++
		}
		//1、交换法
		//str[low], str[high] = str[high], str[low]

		//2、统一交换
		str[low], str[high] = str[high], str[low]

	}
	return low
}

func Partition_1(str []int, low, high int) int {
	//选取基准点。也可以从三个数或者九个数中选取大小为中间值的数作为基准
	pivotkey := str[low]
	temp := pivotkey
	for low < high { //从表的两端交替向中间扫描
		for low < high && str[high] > pivotkey {
			high--
		}
		//替换法
		str[low] = str[high]

		for low < high && str[low] < pivotkey {
			low++
		}
		//替换法
		str[high] = str[low]
	}

	str[low] = temp
	return low
}

func Partition_2(str []int, low, high int) int {
	pivotkey := str[high] // 选择最后一个元素作为基准值
	i := low
	for j := low; j < high; j++ {
		// 将比 pivot 小的数丢到 [l...i-1] 中,剩下的 [i...j] 区间都是比 pivot 大的
		if str[j] < pivotkey {
			// 互换 i、j 下标对应数据
			str[i], str[j] = str[j], str[i]
			// 将 i 下标后移一位
			i++
		}
	}
    // 由于for循环之后i++,所以[l...i-1] 中的值都比pivotkey小,剩下的 [i...j] 区间中的值都是比 pivot 大的
	// 最后将 pivot 与 i 下标对应数据值互换
	// 这样一来,pivot 就位于当前数据序列中间,i 也就是 pivot 值对应的下标
	str[i], str[high] = pivotkey, str[i]
	// 返回 i 作为 pivot 分区位置
	return i
}

二、

1、选择排序

Go 复制代码
// 选择排序
func Select_Sort() {
	str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
	for i := 0; i < len(str)-1; i++ {
		// 未排序区间最小值初始化为第一个元素
		min := i
		// 从未排序区间第二个元素开始遍历,直到找到最小值
		for j := i + 1; j <= len(str)-1; j++ {
			if str[min] > str[j] {
				min = j
			}
		}
		// 将最小值与未排序区间第一个元素互换位置(等价于放到已排序区间最后一个位置)
		if min != i {
			str[min], str[i] = str[i], str[min]
		}
	}
	fmt.Println(str)
}

2、堆排序

算法思路:

堆其实是一颗父节点大于/小于左右子节点的完全二叉树,父节点大于子节点的堆称为大顶堆,父节点小于子节点的堆称为小顶堆。以大顶堆为例,在建堆和调整堆的过程中,我们发现子节点大于父节点时,需要交换父节点与子节点,由于交换后,父节点并不能保证比子节点的子节点更大,因此还需要递归向下比较。当建堆/调整堆完成后,堆顶元素(二叉树的根节点)即为堆中的最大值,我们把最大值取出并重新调整出元素数量为n-1的新大顶堆,再次取出最大值,n轮过后,所有元素都被依次取出,排序完毕

Go 复制代码
//堆排序
func Heap_Sort() {
	str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
	//构建一个大顶堆
	for i := len(str) / 2; i >= 0; i-- {
		Heap_Adjust_2(str, len(str), i)
	}
	for i := len(str) - 1; i > 0; i-- {
		str[0], str[i] = str[i], str[0]
		Heap_Adjust_2(str, i, 0) //将剩余的重新调整为大顶堆,此处的 i 就是len(str) - 1
	}
	fmt.Println(str)
}

//函数0和函数1是同一种思路,只是写法不同
//堆调整函数0
func Heap_Adjust_0(str []int, length, index int) {
	temp := str[index]
	for j := 2*index + 1; j < length-1; j *= 2 { //沿关键字较大的孩子节点向下筛选
		if j < length-1 && str[j] < str[j+1] {
			j++ //j记录孩子节点较大的下标
		}
		if temp > str[j] { //父节点大于孩子节点
			break
		}
		str[index] = str[j] //给节点赋值
		index = j           //index此时已代表孩子节点下标
	}
	str[index] = temp //给孩子节点赋值,到此代表节点的值跟孩子节点中的较大值进行互换完成

}

//堆调整函数1
func Heap_Adjust_1(str []int, length, index int) {
	parent := index
	child := parent*2 + 1
	for ; child < length-1; child *= 2 {
		if child < length-1 && str[child+1] > str[child] {
			child++
		}
		if str[child] > str[parent] {
			str[child], str[parent] = str[parent], str[child]
			parent = child  //最后一个元素和堆顶元素交换后需要向下调整
		}
	}
}

//堆调整函数2
func Heap_Adjust_2(str []int, length, index int) {
	largest := index     // 初始化最大元素为根节点
	left := 2*index + 1  // 左子节点索引
	right := 2*index + 2 // 右子节点索引

	// 如果左子节点大于根节点
	if left < length && str[left] > str[largest] {
		largest = left
	}
	// 如果右子节点大于最大元素
	if right < length && str[right] > str[largest] {
		largest = right
	}
	// 如果最大元素不是根节点
	if largest != index {
		str[index], str[largest] = str[largest], str[index]
		// 递归调整受影响的子树
		Heap_Adjust_2(str, length, largest)
	}
}

func Heap_Adjust_3(str []int, length, index int) {
	for {
		largest := index     // 初始化最大元素为根节点
		left := 2*index + 1  // 左子节点索引
		right := 2*index + 2 // 右子节点索引

		// 如果左子节点大于根节点
		if left < length && str[left] > str[largest] {
			largest = left
		}
		// 如果右子节点大于最大元素
		if right < length && str[right] > str[largest] {
			largest = right
		}
		
		if largest != index {
			str[index], str[largest] = str[largest], str[index]
			index = largest
		}else {
			return
		}
	}
}

三、

1、插入排序

**要点:**设立哨兵,作为临时存储和判断数组边界之用

具体算法描述如下:

  1. 从第一个元素开始,该元素可以认为已经被排序 取出下一个元素
  2. 在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置 将新元素插入到该位置后
  5. 将新元素插入到该位置后
  6. 重复步骤2~5

循环细节:
外层循环

在外层循环中, for i := 1; i < len(arr); i++ ,循环变量 i(下标) 从 1 开始,直到切片 arr 的最后一个元素。这是因为第一个元素 (arr[0]) 默认是已排序的,所以我们从第二个元素 (arr[1]) 开始考虑如何将它插入到前面已排序的序列中

内层循环

内层循环的目的是为了找到新元素 (temp) 在已排序序列中的正确位置,并将这个位置及之后的所有元素向后移动一位,为新元素腾出空间。内层循环的条件是 j >= 0 && arr[j] > temp ,这意味着只要 j 没有越界,并且 arr[j] (已排序序列中的一个元素) 大于新元素 temp,就执行元素的移动操作 (arr[j+1] = arr[j]) 和 j 的递减操作 (j = j - 1)。

当内层循环结束时,j + 1 就是新元素 key 应该插入的位置,因为此时 j 指向的是第一个不大于 temp 的元素,或者 j 已经是 -1 (即已排序序列为空,或者 key 应该插入到序列的最前面)。然后,将 temp 插入到这个位置 (arr[j+1] = temp)

通过外层循环的不断迭代,整个切片 arr 将逐渐变成有序状态

Go 复制代码
//插入排序
func Insert_Sort() {
	str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
	for i := 1; i <= len(str)-1; i++ {
        //要点:设立哨兵,作为临时存储和判断数组边界之用
		temp := str[i]
		j := i - 1
		for ; j >= 0 && str[j] > temp; j-- {
			str[j+1] = str[j]  // 元素后移
		}
		str[j+1] = temp  //插入到正确位置
	}
	fmt.Println(str)
}

2、希尔排序

Go 复制代码
//希尔排序   与插入排序比较,把原来按顺序变成了相隔增量
func Shell_Sort() {
	str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
	//increment相隔数量
	// for increment := len(str) / 3; increment > 0; increment /= 3 {
	increment := len(str)
	for increment > 0 {
		increment = increment / 3
		//此过程类似于插入排序的过程
		for i := increment; i <= len(str)-1; i++ {
			key := str[i]
			j := i - increment
			//按照increment,数组从j到0进行交换比较
			for ; j >= 0 && str[j] > key; j -= increment {
				str[j+increment] = str[j]
			}
			//如果是从for循环走到这里,此时j<0,因为for循环走完时j-=increment ,所以要加回来
            //走到这里时,j已经减掉increment 了,所以要加回来
			str[j+increment] = key
		}
	}
	fmt.Println(str)
}

四、归并排序

Go 复制代码
// 归并排序
func Merge_Sort() {
	str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
	str = Merging_Sort(str)
	fmt.Println(str)
}
func Merging_Sort(str []int) []int {
	// str := []int{9, 1, 5, 8, 3, 7, 4, 6, 2}
	if len(str) <= 1 {
		return str
	}
	mid := len(str) / 2 //获取分区位置
	//进行递归分区
	left := Merging_Sort(str[:mid])
	right := Merging_Sort(str[mid:])
	res := Merge_0(left, right) //函数将两个有序数组合并成一个有序数组

	return res
}

func Merge_0(left, right []int) []int {
	// 用于存放结果集
	result := make([]int, len(left)+len(right))
	i, j, k := 0, 0, 0
	for ; k < len(result); k++ {
		// 任何一个区间遍历完,则退出
		if i >= len(left) || j >= len(right) {
			break
		}
		// 对所有区间数据进行排序
		if left[i] < right[j] {
			result[k] = left[i]
			i++
		} else {
			result[k] = right[j]
			j++
		}
	}
	// 如果左侧区间还没有遍历完,将剩余数据放到结果集
	if i <= len(left) {
		for l := 0; l < len(left)-i; l++ {
			result[k+l] = left[i+l]
		}
	}
	// 如果右侧区间还没有遍历完,将剩余数据放到结果集
	if j <= len(right) {
		for l := 0; l < len(right)-j; l++ {
			result[k+l] = right[j+l]
		}
	}
	return result
}

func Merge_1(left, right []int) []int {
	result := make([]int, len(left)+len(right))
	i, j := 0, 0
	for k := 0; k < len(result); k++ {
		if i >= len(left) {
			result[k] = right[j]
			j++
		} else if j >= len(right) {
			result[k] = left[i]
			i++
		} else if left[i] < right[j] {
			result[k] = left[i]
			i++
		} else {
			result[k] = right[j]
			j++
		}
	}
	return result
}

func Merge_2(left, right []int) []int {
	// result := make([]int, 0, len(left)+len(right))
	//此处不能用make初始化[]int切片时长度必须是0,
	//因为make初始化后,result=[]int{0,0,0},再用append,result=[]int{0,0,0,5},
	//相当于直接在result后面添加数据,会导致结果多了很多0
	result := make([]int, 0, len(left)+len(right))
	//var result []int
	i, j := 0, 0
	for {
		if i >= len(left) {
			result = append(result, right[j:]...)
			break
		} else if j >= len(right) {
			result = append(result, left[i:]...)
			break
		} else if left[i] < right[j] {
			result = append(result, left[i])
			i++
		} else if right[j] < left[i] {
			result = append(result, right[j])
			j++
		}
	}
	return result
}
相关推荐
Excuse_lighttime2 小时前
除自身以外数组的乘积
java·数据结构·算法·leetcode·eclipse·动态规划
万添裁2 小时前
归并排序的三重境界
数据结构·算法
程序员三明治2 小时前
【重学数据结构】队列 Queue
数据结构·后端·算法
杜小暑2 小时前
数据结构之双向链表
c语言·数据结构·后端·算法·链表·动态内存管理
青瓦梦滋2 小时前
【数据结构】哈希——位图与布隆过滤器
开发语言·数据结构·c++·哈希算法
wyiyiyi3 小时前
【数据结构+算法】迭代深度搜索(IDS)及其时间复杂度和空间复杂度
数据结构·人工智能·笔记·算法·深度优先·迭代加深
404未精通的狗3 小时前
(数据结构)链表OJ——刷题练习
c语言·数据结构·链表
洛_尘3 小时前
数据结构--4:栈和队列
java·数据结构·算法
Jiezcode4 小时前
LeetCode 138.随机链表的复制
数据结构·c++·算法·leetcode·链表