数据结构 の 排序
各种算法对比


| 排序方法 | 时间复杂度 | 空间复杂度 | 稳定性 |
|---|---|---|---|
| 直接插入(插入排序) | n2 | 1 | 稳定 |
| 直接选择(选择排序) | n2 | 1 | 不稳定 |
| 堆排序(选择排序) | nlog2 n | 1 | 不稳定 |
| 冒泡排序(交换排序) | n2 | 1 | 稳定 |
| 快速排序(交换排序) | n2 | nlog2 n | 不稳定 |
排序算法的稳定性大家应该都知道,通俗地讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下,如果Ai = Aj,Ai原来在位置前,排序后Ai还是要在Aj位置前。
选择排序:序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
具体的排序的稳定性,可以查考下面的文章。
https://www.cnblogs.com/codingmylife/archive/2012/10/21/2732980.html
插入排序
就想是在军训排队的时候,已经排好队了,这时又有人临时加如这个队中来,于是教官大喊:“新来大,迅速找到你的位置,并插入进去”。 这种排序方式,包括 直接插入排序,折半插入排序,希尔排序。
直接插入排序
package main
import (
"fmt"
"math/rand"
"time"
)
// 插入排序就相当于用 R[i] 做分界线,他前面的是有序的,后面的是无序的
func InsertSort(R []int) {
for i := 1; i < len(R); i++ { //第一个肯定是有序的
temp := R[i]
j := i - 1
for j >= 0 && temp < R[j] { //这个地方必须要有 j==0
R[j+1] = R[j]
j--
}
R[j+1] = temp
}
}
func main() {
rand.Seed(time.Now().UnixNano()) // 随机数种子
a := make([]int, 15)
for i := 0; i < 15; i++ {
a[i] = rand.Intn(100) // 插入100以内的随机数
}
fmt.Println(a)
InsertSort(a)
fmt.Println(a)
}
## 输出结果
[35 36 7 75 28 36 90 58 59 35 61 32 64 79 17]
[7 17 28 32 35 35 36 36 58 59 61 64 75 79 90]
复杂度分析
时间复杂度:用 R[j+1]=R[j] 这一句作为基本操作,那么时间复杂度,最大是(n2)
空间复杂度: 辅助存储空间不随排序规模的扩大而扩大,因此是个常量,空间复杂度为 O(1)
折半插入排序
交换排序
冒泡排序
func Sort(R []int) {
for i := 0; i < len(R); i++ {
for j := i + 1; j < len(R); j++ {
if R[i] > R[j] {
// 冒泡排序就是相当于把每一次都把 i 后面的最小的一个给选出来。
R[i], R[j] = R[j], R[i]
}
}
}
}
func main() {
rand.Seed(time.Now().UnixNano())
r := make([]int, 15)
for i := 0; i < 15; i++ {
r[i] = rand.Intn(100)
}
fmt.Println(r)
Sort(r)
fmt.Println(r)
}
## 时间复杂度
时间复杂度 O(n2)
空间复杂度 O(1)
快排的经典写法
func quickSort(a []int) {
if len(a) <= 1 {
return
}
pivot := a[0]
i, j := 0, len(a)-1
for i < j {
for i < j && a[j] >= pivot {
j--
}
for i < j && a[i] <= pivot {
i++
}
a[i], a[j] = a[j], a[i]
}
a[0], a[i] = a[i], pivot
quickSort(a[:i])
quickSort(a[i+1:])
}
func main() {
a := make([]int, 10)
rand.New(rand.NewSource(time.Now().UnixMicro()))
for i := range 10 {
a[i] = rand.Intn(100)
}
fmt.Println(a)
quickSort(a)
fmt.Println(a)
}
快速排序
func quickSort(nums []int) []int {
if len(nums) <= 1 {
return nums
}
pivot := nums[0]
var low, mid, high []int
for _, v := range nums {
switch {
case v > pivot:
high = append(high, v)
case v < pivot:
low = append(low, v)
default:
mid = append(mid, v)
}
}
low = quickSort(low)
high = quickSort(high)
// 中间的 mid 就不需要排了
return append(append(low, mid...), high...)
}
func main() {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
a := make([]int, 10)
for i := 0; i < 10; i++ {
a[i] = r.Intn(100)
}
fmt.Println(a)
fmt.Println(strings.Repeat("*", 10))
fmt.Println(quickSort(a))
}
快排的第二种写法
func main() {
rand.Seed(time.Now().UnixNano())
numb := make([]int, 10)
for i := 0; i < 10; i++ {
numb[i] = rand.Intn(100)
}
fmt.Println(numb)
var quick_sort func(left, right int)
quick_sort = func(left, right int) {
if left >= right {
return
}
i, j := left, right
pivot := numb[i]
for i < j {
for i < j && numb[j] >= pivot {
j--
}
if i < j {
numb[i] = numb[j]
i++
}
for i < j && numb[i] < pivot { //j有等于了,左边就不写了
i++
}
if i < j {
numb[j] = numb[i]
j--
}
}
numb[i] = pivot
quick_sort(left, i-1)
quick_sort(i+1, right)
}
quick_sort(0, len(numb)-1)
fmt.Println(numb)
}
并归排序
package main
import (
"fmt"
)
// MergeSort 归并排序入口函数:接收待排序切片,返回排序后的新切片
func MergeSort(arr []int) []int {
// 递归终止条件:切片长度 <=1 时,直接返回(已有序)
if len(arr) <= 1 {
return arr
}
// 1. 分:将数组拆分为左右两个子数组
mid := len(arr) / 2 // 找到中间索引
left := MergeSort(arr[:mid]) // 递归排序左半部分
right := MergeSort(arr[mid:]) // 递归排序右半部分
// 2. 治:合并两个有序子数组
return merge(left, right)
}
// merge 核心合并函数:将两个有序切片合并为一个有序切片
func merge(left, right []int) []int {
// 初始化结果切片,容量为左右切片长度之和
result := make([]int, 0, len(left)+len(right))
// 双指针分别遍历左右切片
i, j := 0, 0
// 循环比较左右切片的元素,取较小值加入结果
for i < len(left) && j < len(right) {
if left[i] <= right[j] {
result = append(result, left[i])
i++
} else {
result = append(result, right[j])
j++
}
}
// 处理左切片剩余元素(若有)
result = append(result, left[i:]...)
// 处理右切片剩余元素(若有)
result = append(result, right[j:]...)
return result
}
func main() {
arr1 := []int{8, 4, 5, 7, 1, 3, 6, 2}
fmt.Println("原始数组1:", arr1)
sortedArr1 := MergeSort(arr1)
fmt.Println("排序后数组1:", sortedArr1)
}
k路并归s
func main() {
a := [][]int{
{1, 12, 15, 24, 29},
{19, 25, 36, 38, 33},
{4, 9, 14, 16, 23},
}
ksort := func(numbs [][]int) []int {
n, m := len(a), len(a[0])
indexlist := make([]int, n)
ret := []int{}
for {
index := -1
minub := 1 << 32
for i := 0; i < n; i++ {
if indexlist[i] < m && numbs[i][indexlist[i]] < minub {
minub = numbs[i][indexlist[i]] //最小的数
index = i
}
}
if index == -1 {
break
}
indexlist[index]++
ret = append(ret, minub)
}
return ret
}
ret := ksort(a)
fmt.Println(ret)
}
加了随机化的快排
func main() {
rand.Seed(time.Now().UnixNano())
numb := make([]int, 10)
for i := 0; i < 10; i++ {
numb[i] = rand.Intn(100)
}
fmt.Println(numb)
var quick_sort func(left, right int)
quick_sort = func(left, right int) {
if left >= right {
return
}
i, j := left, right
index := rand.Int()%(right-left+1) + left
numb[i], numb[index] = numb[index], numb[i]
privot := numb[i]
for i < j {
for i < j && numb[j] >= privot {
j--
}
if i < j {
numb[i] = numb[j]
i++
}
for i < j && numb[i] < privot { //j有等于了,左边就不写了
i++
}
if i < j {
numb[j] = numb[i]
j--
}
}
numb[i] = privot
quick_sort(left, i-1)
quick_sort(i+1, right)
}
quick_sort(0, len(numb)-1)
fmt.Println(numb)
}
复杂度分析:
最好的情况下:时间复杂度为 O(nlog2n),待排序列越接近于无序,本算法的效率就越高,最快情况下为 O(n2)。平均复杂度为 O(nlog2n)
选择类排序
选择排序的最主要动作就是选择。
简单选择排序
选择排序就是选择最小的。
func Sort(R []int) {
for i := 0; i < len(R); i++ {
k := i
for j := i + 1; j < len(R); j++ {
if R[j] < R[k] {
k = j // 这个地方记录的是k,而不是直接交换的数组,这是和冒泡最大的区别
}
}
R[k], R[i] = R[i], R[k]
}
}
时间和空间复杂度和冒泡都一样,时间复杂度都是 n2 ,空间复杂度是 O(1)
堆排序

// 从 R[low]到R[high]的范围内对位置在low上的节点进行调整
// 没执行一直这个函数,就相当于位于 low 的这个点彻底调完,hight就是后来帮忙的。
func Sift(R []int, low, high int) {
i, j := low, 2*low // 树的节点,默认是从1开始的。
temp := R[i]
for j <= high {
if R[j] < R[j+1] && j < high { // 从左右节点中挑出来一个最大的
j++ // 变成右节点
}
if temp < R[j] {
R[i] = R[j] // 把j调整到双亲节点上
i = j // i是要放的节点 继续往下调整
j = i * 2
} else {
break
}
}
R[i] = temp // 把调整后的节点放在最终位置
}
func heapSort(R []int) {
// 调整顺序,先下后上,先右后左
n := len(R) - 1 // 因为序号要比次数-1,比如第5个节点,应该是 R[4]
for i := n / 2; i >= 1; i-- { // 叶子节点肯定都是堆,所以从 n/2 开始,应是从下到上,从右往左切换
Sift(R, i, n) // 循环调整完以后,最大的肯定在最上面。
}
for i := n; i >= 2; i-- {
R[1],R[i]=R[i],R[1]
Sift(R, 1, i-1) //把最大的和最后面的进行替换调整
}
}
func main() {
rand.Seed(time.Now().UnixNano())
r := make([]int, 20)
r[0] = 0
for i := 1; i < 20; i++ {
r[i] = rand.Intn(100)
}
fmt.Println(r)
heapSort(r)
fmt.Println(r)
}
查找
折半查找
func Bsearch(R []int, k int) int {
low := 0
high := len(R)
for low <= high {
mid := (low + high) / 2
if R[mid] == k {
return mid
} else if R[mid] > k {
high = mid - 1
} else {
low = mid + 1
}
}
return -1
}
func Sort(R []int) {
for i := 0; i < len(R); i++ {
for j := i + 1; j < len(R); j++ {
if R[j] < R[i] {
R[j], R[i] = R[i], R[j]
}
}
}
}
func main() {
rand.Seed(time.Now().Unix())
r := make([]int, 20)
for i := 0; i < len(r); i++ {
r[i] = rand.Intn(100)
}
fmt.Println(r)
Sort(r)
fmt.Println(r)
rel := Bsearch(r, r[4])
fmt.Println(rel)
}
二叉排序树
- 若它的左子树不为空,则左子树的所有关键点的值均不大于根关键点的值
- 若它的右子树不为空,则右子树的所有关键点的值均不大于根关键点的值
- 左右子树又各是一棵二叉排序树
type BTNode struct {
data int
lchild *BTNode
rchild *BTNode
}
func BTsearch(btnode *BTNode, k int) *BTNode { //注意返回类型
if btnode == nil {
return nil
} else {
if btnode.data == k {
return btnode
} else if k < btnode.data {
return BTsearch(btnode.lchild, k)
} else {
return BTsearch(btnode.rchild, k)
}
}
}
浙公网安备 33010602011771号