2.1. 排序
2.1.1. 选择排序
- SelectionSort 函数:这个函数使用选择排序算法对整数数组进行排序。它通过遍历数组,每次找到最小的元素并将其放到正确的位置。
- 循环:
外循环:从数组的第一个元素开始,逐渐处理到倒数第二个元素。
内循环:在未排序的部分中找到最小的元素。 - 找到最小元素:通过内循环比较每个元素,更新最小元素的索引。
- 交换元素:将最小元素与当前位置的元素交换,从而将数组逐渐排序。
- main 函数:在这里进行了测试,创建了一个示例数组,然后调用 SelectionSort 函数对其进行排序,并输出排序前后数组的内容。
package main
import "fmt"
// SelectionSort 使用选择排序算法对整数数组进行排序
func SelectionSort(arr []int) {
// 遍历未排序部分的索引范围
for i := 0; i < len(arr)-1; i++ {
// 假设当前索引位置的元素为最小值的索引
minIndex := i
// 在未排序部分找到最小值的索引
for j := i + 1; j < len(arr); j++ {
if arr[j] < arr[minIndex] {
minIndex = j
}
}
// 将找到的最小值与当前位置的元素交换
arr[i], arr[minIndex] = arr[minIndex], arr[i]
}
}
func main() {
// 测试选择排序算法
arr := []int{64, 25, 12, 22, 11}
fmt.Println("原始数组:", arr)
SelectionSort(arr)
fmt.Println("排序后的数组:", arr)
}
2.1.2. 冒泡排序
- BubbleSort 函数使用冒泡排序算法对传入的整数数组 arr 进行排序。
- 在外层循环中,通过 for 循环遍历数组,每次循环将找到的最大值放在末尾,因此循环次数为 n-1 次,其中 n 是数组的长度。
- 在内层循环中,通过 for 循环控制比较和交换元素的过程,每次循环会比较相邻的两个元素,并将较大的元素交换到后面。
- 每次内层循环结束后,数组的最大值都会冒泡到未排序部分的末尾,因此每次遍历都会有一个元素被放置在正确的位置上。
- 在 main 函数中,通过传入测试数组并调用 BubbleSort 函数来测试冒泡排序算法的正确性。
package main
import "fmt"
// BubbleSort 使用冒泡排序算法对整数数组进行排序
func BubbleSort(arr []int) {
// 数组长度
n := len(arr)
// 外层循环控制遍历次数
for i := 0; i < n-1; i++ {
// 内层循环控制比较并交换元素的过程
// 每次遍历会将最大的元素冒泡到未排序部分的末尾
for j := 0; j < n-i-1; j++ {
// 比较相邻的两个元素,如果顺序不对则交换它们
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j] // 交换 arr[j] 和 arr[j+1]
}
}
}
}
func main() {
// 测试冒泡排序算法
arr := []int{64, 25, 12, 22, 11}
fmt.Println("原始数组:", arr)
BubbleSort(arr)
fmt.Println("排序后的数组:", arr)
}
2.1.3. 插入排序(时间复杂度O(N2)、空间复杂度O(1))
package main
import "fmt"
// InsertionSort 对传入的整数切片进行插入排序
func InsertionSort(arr []int) {
n := len(arr)
if n <= 1 {
return // 如果数组长度小于等于1,则无需排序
}
for i := 1; i < n; i++ {
key := arr[i] // 当前待插入的值
j := i - 1 // 已排序部分的最后一个元素索引
// 将大于待插入值的元素依次后移,为待插入值腾出位置
for j >= 0 && arr[j] > key {
arr[j+1] = arr[j]
j--
}
// 将待插入值插入到正确的位置
arr[j+1] = key
}
}
func main() {
arr := []int{12, 11, 13, 5, 6}
fmt.Println("未排序前:", arr)
InsertionSort(arr)
fmt.Println("排序后:", arr)
}
2.1.4. 归并排序
2.1.4.1. 图解
2.1.4.2. 代码实现
package main
import "fmt"
// MergeSort 对传入的整数切片进行归并排序
func MergeSort(arr []int) {
n := len(arr)
if n <= 1 {
return // 如果数组长度小于等于1,则无需排序
}
// 递归地将数组分成两半
mid := n / 2
left := make([]int, mid)
right := make([]int, n-mid)
copy(left, arr[:mid])
copy(right, arr[mid:])
// 递归排序左右两半部分
MergeSort(left)
MergeSort(right)
// 合并已排序的左右两部分
merge(arr, left, right)
}
// merge 函数用于将已排序的左右两部分合并成一个有序数组
func merge(arr, left, right []int) {
i, j, k := 0, 0, 0
nLeft, nRight := len(left), len(right)
// 将左右两部分按顺序合并成一个有序数组
for i < nLeft && j < nRight {
if left[i] <= right[j] {
arr[k] = left[i]
i++
} else {
arr[k] = right[j]
j++
}
k++
}
// 处理剩余的元素
for i < nLeft {
arr[k] = left[i]
i++
k++
}
for j < nRight {
arr[k] = right[j]
j++
k++
}
}
func main() {
arr := []int{12, 11, 13, 5, 6, 7}
fmt.Println("未排序前:", arr)
MergeSort(arr)
fmt.Println("排序后:", arr)
}
2.1.5. 堆排序
2.1.5.1. 代码实现
堆排序是一种基于堆数据结构的排序算法,它利用了堆的特性来实现排序。堆是一种特殊的树形数据结构,具有以下特点:
- 堆是一个完全二叉树,即除了最后一层,其它层的节点都是满的,且最后一层的节点都尽量靠左排列。
- 在最大堆中,父节点的值始终大于或等于其子节点的值。
- 在最小堆中,父节点的值始终小于或等于其子节点的值。
堆排序的基本思想是:
- 将待排序的序列构建成一个最大堆(或最小堆)。
- 依次将堆顶元素(最大值或最小值)与堆中最后一个元素交换,并将堆的大小减一,然后对堆进行调整,使得堆重新满足堆的性质。
- 重复步骤 2,直到堆的大小为 1,即所有元素都已经有序。
堆排序的实现可以分为两个主要步骤:
- 建堆(Heapify):将待排序的序列构建成一个堆,通常采用自底向上的方式从最后一个非叶子节点开始进行调整,直到根节点。
- 排序:重复执行以下操作,直到堆的大小为 1:
-
- 将堆顶元素与堆中最后一个元素交换。
- 将堆的大小减一。
- 对交换后的堆进行调整,使得它重新满足堆的性质。
堆排序的时间复杂度为 O(nlogn),其中 n 为序列的长度。堆排序是一种原地排序算法,不需要额外的空间开销,但它不是稳定的排序算法。
package main
import "fmt"
// 堆排序函数
func HeapSort(arr []int) {
// 构建最大堆
for i := 1; i < len(arr); i++ {
heapInsert(arr, i)
}
// 依次将堆顶元素与最后一个元素交换,并调整堆
for i := len(arr) - 1; i > 0; i-- {
arr[0], arr[i] = arr[i], arr[0]
heapify(arr, 0, i)
}
}
// 向上调整,维护最大堆性质
func heapInsert(arr []int, index int) {
for arr[index] > arr[(index-1)/2] { // 当前节点大于其父节点
arr[index], arr[(index-1)/2] = arr[(index-1)/2], arr[index]
index = (index - 1) / 2
}
}
// 向下调整,维护最大堆性质
func heapify(arr []int, index, size int) {
left := 2*index + 1
for left < size {
largest := left
right := left + 1
if right < size && arr[right] > arr[left] { // 右孩子存在且大于左孩子
largest = right
}
if arr[index] >= arr[largest] { // 当前节点大于等于孩子节点中的最大值
break
}
arr[index], arr[largest] = arr[largest], arr[index]
index = largest
left = 2*index + 1
}
}
func main() {
arr := []int{3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5}
HeapSort(arr)
fmt.Println(arr) // 输出:[1 1 2 3 3 4 5 5 5 6 9]
}
2.1.6. shell排序
2.1.6.1. 图解
2.1.6.2. 代码实现
Shell排序是插入排序的一种改进版本,也被称为缩小增量排序。它通过将待排序的数组分成多个子序列,对每个子序列进行插入排序,然后逐步缩小子序列的长度,最终完成排序。Shell排序的时间复杂度取决于增量序列的选择,常见的增量序列包括希尔增量序列(h = h/3+1)和Sedgewick增量序列(1, 5, 19, 41, 109...)等。
这段代码实现了希尔排序算法,通过不断缩小增量 h 的方式,对子序列进行插入排序,最终完成整个数组的排序。在主函数中,我们定义了一个整数切片 arr,对其调用 shellSort 函数进行排序,并输出排序后的结果。
希尔排序的时间复杂度约为 O(n^1.3),比插入排序的时间复杂度 O(n^2) 要好,尤其是对大规模数据进行排序时效果明显。
package main
import "fmt"
// shellSort 使用希尔增量序列对传入的整数切片进行排序
func shellSort(arr []int) {
n := len(arr)
// 计算初始增量,采用希尔增量序列 h = h/3 + 1
h := 1
for h < n/3 {
h = h*3 + 1
}
// 对每个增量进行排序
for h >= 1 {
// 对每个子序列进行插入排序
for i := h; i < n; i++ {
// 将 arr[i] 插入到 arr[i-h], arr[i-2*h], arr[i-3*h]... 中合适的位置
for j := i; j >= h && arr[j] < arr[j-h]; j -= h {
arr[j], arr[j-h] = arr[j-h], arr[j]
}
}
// 缩小增量
h /= 3
}
}
func main() {
arr := []int{5, 2, 4, 6, 1, 3}
fmt.Println("原数组:", arr)
shellSort(arr)
fmt.Println("排序后数组:", arr)
}
2.1.7. 快速排序
package main
import "fmt"
// 快速排序函数
func quickSort(arr []int, low, high int) {
if low < high {
// 划分
pivot := partition(arr, low, high)
// 递归排序左半部分
quickSort(arr, low, pivot-1)
// 递归排序右半部分
quickSort(arr, pivot+1, high)
}
}
// 划分函数
func partition(arr []int, low, high int) int {
// 选取最后一个元素作为基准值
pivot := arr[high]
// i 指向小于基准值的元素的位置
i := low - 1
// 遍历数组,将小于基准值的元素交换到左边
for j := low; j <= high-1; j++ {
if arr[j] < pivot {
i++
arr[i], arr[j] = arr[j], arr[i]
}
}
// 将基准值交换到正确的位置
arr[i+1], arr[high] = arr[high], arr[i+1]
return i + 1
}
func main() {
arr := []int{9, 7, 5, 11, 12, 2, 14, 3, 10, 6}
fmt.Println("Unsorted array:", arr)
// 调用快速排序函数
quickSort(arr, 0, len(arr)-1)
fmt.Println("Sorted array:", arr)
}
2.1.8. 桶排序
2.1.8.1. 计数排序
2.1.8.1.1. 图解
2.1.8.1.2. 代码实现
计数排序是一种非比较排序算法,适用于一定范围内的整数排序。它的基本思想是统计每个整数在序列中出现的次数,然后根据统计信息将这些整数按顺序输出到结果数组中。
以下是计数排序的基本步骤:
- 找出待排序数组中的最大值 max 和最小值 min,并根据它们确定计数数组的大小。
- 初始化一个计数数组 count,长度为 max - min + 1,并将所有元素初始化为0。
- 遍历待排序数组,统计每个元素出现的次数,将计数结果存储在计数数组中。
- 根据计数数组中的统计信息,将待排序数组中的元素按顺序放入结果数组中。
- 将结果数组中的元素复制回原始数组,完成排序。
package main
import "fmt"
func countSort(arr []int) {
if len(arr) == 0 {
return
}
// 找出数组中的最大值和最小值
min, max := arr[0], arr[0]
for _, num := range arr {
if num < min {
min = num
}
if num > max {
max = num
}
}
// 初始化计数数组
count := make([]int, max-min+1)
// 统计每个元素出现的次数
for _, num := range arr {
count[num-min]++
}
// 根据计数数组构造排序后的结果
index := 0
for i := min; i <= max; i++ {
for count[i-min] > 0 {
arr[index] = i
index++
count[i-min]--
}
}
}
func main() {
arr := []int{4, 2, 2, 8, 3, 3, 1}
fmt.Println("原数组:", arr)
countSort(arr)
fmt.Println("排序后数组:", arr)
}