排序算法
冒泡排序
- BubbleSort()
算法:
- 外层控制行 len(arr)-1
- 内层控制列 len(arr)-1-i
- 相邻数据比大小
- 满足条件进行交换
// 冒泡排序
func BubbleSort(arr []int) {
// 外层控制行
for i:=0; i<len(arr)-1; i++ {
// 内层控制列
for j:=0; j<len(arr)-1-i; j++ {
// 相邻数据比大小
if arr[j] > arr[j+1] {
// 满足条件交换
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
}
优化冒泡排序:
// 冒泡排序优化
func BubbleSort2(arr []int) {
count := 0
flg := false
// 外层控制行
for i := 0; i < len(arr)-1; i++ {
// 内层控制列
for j := 0; j < len(arr)-1-i; j++ {
count++
// 相邻数据比大小
if arr[j] > arr[j+1] {
// 满足条件交换
arr[j], arr[j+1] = arr[j+1], arr[j]
flg = true
}
}
if !flg {
fmt.Println("count:", count)
return // 结束整个 冒泡排序函数. 没有数据需要交换.
} else {
flg = false
}
}
}
选择排序
- SelectSort()
- 外层控制行
- 内层控制列
- 将第一个元素当做最大数
- 使用该数,一次向后比较,找真正的最大数, 记录下标index。
- 当一行比较结束。将index对应值,写到最后一个位置。
- 上面所示动图为找到最小的数放到最前面(原理一样)
func SelectSort(arr []int) {
// 外层控制行
for i := 0; i < len(arr)-1; i++ {
// 定义idnex 记录最大值下标
index := 0
// 内层控制列
for j := 1; j < len(arr)-i; j++ {
if arr[j] > arr[index] {
index = j // 找到比index 对应的数据大的数据, 更新index
}
}
// 将 index 存储的数据,和最后一个元素交换.
arr[index], arr[len(arr)-i-1] = arr[len(arr)-i-1], arr[index]
}
}
插入排序
- InsertSort()
算法:
- 将待排序列分成 “有序数据组”(第一个元素)、“无序数据组”
- 取无序数据组 第一个元素,与有序数据组的 最后一个元素比较。
- 如果 小于 ,将无序数据组的数据,插入到有序数据组中。
- 依次判断有序数据组以前的数据,是否大于新插的数据
- 重复上述 2- 4步。
func InsertSort(arr []int) {
// 把arr的第一个元素,作为有序数据组
for i := 1; i < len(arr); i++ {
// 无序数据 与 前一个有序数据 比较. 只有小于时,需要完成插入操作
if arr[i] < arr[i-1] {
j := i - 1 // j 保存,相邻有序数据的的下标
temp := arr[i] // 待插入的 无序元素
for j >= 0 && arr[j] > temp { // 将有序数据,依次与无序数据比较
arr[j+1] = arr[j] // 不断赋值, 保留 待插入的无序数据的位置.
j--
}
arr[j+1] = temp
}
}
}
方法二(两两比较互换):
func InsertSort2(arr []int) {
for i := 1; i < len(arr); i++ {
if arr[i] < arr[i-1] { // 无序数据,小于有序数据
for j := i; j > 0; j-- {
if arr[j] < arr[j-1] { // 利用冒泡思想,相邻两两比较.交换
arr[j], arr[j-1] = arr[j-1], arr[j]
}
}
}
}
}
计数排序
- CountingSort()
- 也叫变相插入排序 DisguisedFormSort()
- 计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
应用场景:
举例:
有10万个数,每个数值的取值范围0-999。将该10万个乱序的数据集排序成有序数据集,输出。
算法:
-
定义map用来统计 10 万个数中,各个数出现的次数
-
遍历10万个数,以0-999为 map的key,用value累计 key 对应的数值出现的次数。
-
循环从0-999,按各个数出现的次数打印输出。
// 计数统计排序
func main() {
s := make([]int, 0)
rand.Seed(time.Now().UnixNano()) // 播种随机数种子
// 循环生成100000个随机数
for i := 0; i < 100000; i++ {
s = append(s, rand.Intn(1000))
}
fmt.Println(s)
// 创建 map 统计 1-999 每个数出现的 次数.
m := make(map[int]int)
// 遍历 切片,统计各个数出现的次数,使用 map 记录
for i := 0; i < len(s); i++ {
m[s[i]]++
}
fmt.Println("\n", m)
count := 0
// 按序借助map 输出所有数据
for i := 0; i < 1000; i++ {
// 内层循环按 每个数,出现的次数打印
for j := 0; j < m[i]; j++ {
count++
fmt.Print(i, " ")
}
}
fmt.Println("\n", count)
}
借助slice实现
//maxValue 为统计数的最大值
func countingSort(arr []int, maxValue int) {
bucket := make([]int, maxValue + 1) // 初始为0的数组
sortedIndex := 0
length := len(arr)
//计数统计
for i := 0; i < length; i++ {
bucket[arr[i]] += 1
}
//统计好的数放回原数组
for j := 0; j < len(bucket); j++ {
for bucket[j] > 0 {
arr[sortedIndex] = j
sortedIndex += 1
bucket[j] -= 1
}
}
}
希尔排序
- 缩小增量(increment)排序
- ShellSort()
算法:
- 使用数组长度 / 2 , 得到初始增量。
- 将 0 的元素 和 增量值对应的元素,比大小。交换
- 顺序 按增量依次 后移 ,成对数据比较。
- 修改增量 。 /2 .
func ShellSort1(arr []int) {
// 获取增量 (increment) len(arr)/2 --- len(arr)/2/2 --- len(arr)/2/2/2
for inc := len(arr) / 2; inc > 0; inc /= 2 { // 5 2 1
// 获取比较的增量元素下标
for i := inc; i < len(arr); i++ { // 5,6,7,8,9
temp := arr[i]
for j := i - inc; j >= 0; j -= inc { // 0,1,2,3,4
if temp < arr[j] {
arr[j], arr[j+inc] = arr[j+inc], arr[j]
temp=arr[j]
} else {
break
}
}
}
}
}
快速排序
- QuickSort()
算法:
- 选定第一个元素为基准值
- 从第二个元素开始,向后遍历数组,比基准值小,继续后移,比基准值大,停止。
- 从尾元素开始,向前遍历数组,比基准值大,继续前移,比基准值小,停止。与上步找到的元素交换。
- 重复 2/3步,直到 向后遍历和向前遍历重合。分区。
- 对应分区,重新确定基准值,重新执行上述 1-5步。
排序过程步骤总结:
- 默认 第 0 个元素“基准值”。 定义 temp 存 值 , index 存 下标。
- 起始下标:0 从左 → 假定【i】
- 结束下标:len(arr) -1 从右 ← 假定【j】。 【i】、【j】对着走。
- 如果【i】对应元素 < “基准值” 【i】→ 取下一个。
- 如果【i】对应元素 > “基准值” 【i】停住不动。 拿【j】与“基准值”比较。
- 如果【j】对应元素 > “基准值” 【j】← 取下一个。
- 如果【j】对应元素 < “基准值” 将【i】对应元素 与 【j】对应元素 “交换”!!!
- 继续【i】→ 取元素,与基准值比较 , 重复 4 - 7 步。
- 直到【i】、【j】重合,指向同一个元素。 该元素以前的 所有元素都 <“基准值”; 该元素(包含)以后的 所有元素都 > “基准值”
- 将“基准值”与 【i】、【j】的元素的前一个元素交换。此时“基准值”在 数组中的位置“永久”确定。左侧都比它小,右侧都比它大。
- 将“基准值”以前的所有数据看成集合,再次结合【i】、【j】重复 1 - 10 步。 又能“永久”确定一个元素位置。是一个递归操作。
- 将“基准值”以后的所有数据看成集合,结合【i】、【j】重复 1 - 10 步。再“永久”确定一个元素位置。也是一个递归。
- 以此类推,不断重复上述过程。就可以将整个数据不断“分块”,找出块最大,并排成有序集合。
- 需要递归实现。
func QuickSort(arr []int, left, right int) {
// 设置基准值。因为要递归调用,不能使用 0
temp := arr[left] // 4
index := left // 7
i := left // 起始下标 // 0
j := right // 结束下标 // 9
// 循环比较基准值。i <= j 时都有比较操作。
for i <= j {
// 自右向左,找到比基准值小的数据
for j >= index && arr[j] >= temp {
j--
}
// 上循环结束,找到了小数据,写到前面
if j > index {
arr[index] = arr[j]
index = j
}
// 自左向右,找到比基准值大的数据
for i <= index && arr[i] <= temp {
i++
}
// 上循环结束,找到了大数据,写到后面
if i <= index {
arr[index] = arr[i]
index = i
}
}
//(for结束) 将基准值放在永久位置
arr[index] = temp
if index-left > 1 { // 两个以上数据才有必要比较
// 递归处理 基准值 新位置以左 的数据集
QuickSort(arr, left, index-1)
}
if right-index > 1 { // 两个以上数据才有必要比较
// 递归处理 基准值 新位置以右 的数据集
QuickSort(arr, index+1, right)
}
}
方法2(不需要temp):
func quickSort2(arr []int, start, end int) {
if start < end {
//起始下标与结束下标
i, j := start, end
//设置基准值
key := arr[(start+end)/2]
// 循环比较基准值。i <= j 时都有比较操作。
for i <= j {
// 自左向右,找到比基准值大的数据
for arr[i] < key {
i++
}
// 自右向左,找到比基准值小的数据
for arr[j] > key {
j--
}
//循环完成,将比基准值大的数据和比基准值小的数据互换位置
if i <= j {
arr[i], arr[j] = arr[j], arr[i]
i++
j--
}
}
//此时基准值前边的都要比基准值小,后面的都要比基准值大,下面就是递归处理
if start < j {
quickSort2(arr, start, j)
}
if end > i {
quickSort2(arr, i, end)
}
}
}
堆排序
- HeapSort()
- 最大堆、大顶堆、大根堆:将数据序列按二叉树存储,各个子树的根节点,均保存该子树最大值。
- 最小堆、小顶堆、小根堆:。。。。。。。。。。。。 最小值。
算法:
- 将线性表,转换成 完全二叉树存储。
- 从非叶子结点开,依次采用大根堆比较子树的(最多3个结点)。最大值保存在树的根节点
- 将根结点数据,与最后一个叶子结点数据交换。确定一个最大数。
- 重复上述 2-3步。
循环实现:
// 大顶堆排序
func HeapSort(arr []int) {
length := len(arr) // 获取数组长度
// 将切片,转成二叉树模型。 实现大根堆(最大堆、大顶堆)
for i := length/2-1; i >= 0; i-- { // 第一个非叶子节点的序号为length/2-1 —— 3、2、1、0
heapAdjustDown(arr, i, length-1)
}
// 进行堆排序
for i := length - 1; i > 0; i-- {
// 堆顶元素和最后一个元素交换位置, 最后一个位置保存最大数
arr[0], arr[i] = arr[i], arr[0]
// 将 arr[0...i-1] 重新调整为最大堆
heapAdjustDown(arr, 0, i-1)
}
}
func heapAdjustDown(arr []int, start int, end int) {
temp := arr[start]; // 保存当前结点
i := 2*start + 1; // 该结点的“左孩子”在数组中的位置序号
for i <= end {
// 找左、右孩子中的最大,用 i 记录位置序号
if i+1 <= end && arr[i+1] > arr[i] { // i+1 " 右孩子" 的位置序号
i++; // 右孩子大, i 记录右孩子位置序号
}
// 如果符合大顶堆定义(左、右均 <= 父结点),则不用调整位置
if arr[i] <= temp {
break;
}
// 最大子结点,替换掉其父结点
arr[start] = arr[i];
start = i;
// 从 上->下 找
i = 2*start + 1;
}
arr[start] = temp;
}
func main() {
arr := []int{6, 9, 10, 2, 7, 1, 3, 5, 8}
HeapSort(arr)
fmt.Println(arr)
}
递归实现:
//初始化堆
func HeapInit(arr []int) {
//将切片转成二叉树模型 实现大根堆
length := len(arr)
for i := length/2 - 1; i >= 0; i-- { // 4,3,2,1
HeapSort2(arr, i, length-1)
}
//根节点存储最大值
for i := length - 1; i > 0; i-- {
//如果只剩下根节点和跟节点下的左子节点
if i == 1 && arr[0] <= arr[i] {
break
}
//将根节点和叶子节点数据交换
arr[0], arr[i] = arr[i], arr[0]
HeapSort2(arr, 0, i-1)
}
}
//获取堆中最大值 放在根节点
func HeapSort2(arr []int, startNode int, maxNode int) {
// 最大值放在根节点
var max int
// 定义左子节点和右子节点
lChild := startNode*2 + 1
rChild := lChild + 1
// 子节点超过比较范围 跳出递归
if lChild >= maxNode {
return
}
// 左右比较 找到最大值
if rChild <= maxNode && arr[rChild] > arr[lChild] {
max = rChild
} else {
max = lChild
}
// 和跟节点比较
if arr[max] <= arr[startNode] {
return
}
// 交换数据
arr[startNode], arr[max] = arr[max], arr[startNode]
// 递归进行下次比较
HeapSort2(arr, max, maxNode)
}
func main() {
arr := []int{6, 9, 10, 2, 7, 1, 3, 5, 8, 4}
HeapInit(arr)
fmt.Println(arr)
}
二分法查找
- BinarySearch()
- 只适用于 有序数据集。一般是先排序,再查找。
import "fmt"
//二分查找 BinarySearch(数据,元素) 返回值为下标
func BinarySearch(arr []int, num int) int {
start := 0 //定义起始下标
end := len(arr) - 1 //定义结束下标
mid := (start + end) / 2 //定义中间基准值
for i := 0; i < len(arr); i++ {
if num == arr[mid] { //中间基准值直接为查找值
return mid //返回下标
} else if num > arr[mid] { //查找值 比基准值大
start = mid + 1 //查找右侧
} else { //查找值 比基准值小
end = mid - 1 //查找左侧
}
mid = (start + end) / 2 //根据新起始、结束下标,再次设置中间基准值
}
return -1 //没有找到
}