〇、汇总
排序算法 | 原理 | 平均 | 最坏 | 空间 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | 相邻元素交换,大的交换到后面【你行你上】 | O(n²) | O(n²) | O(1) | 稳定 |
选择排序 | 未排序中选择最大的到后面 【最菜的前面去】 | - | - | - | - |
插入排序 | 一个记录插入到已排好序的表中 【扑克牌理牌】 | - | - | - | - |
快速排序 | 分治法,找基准 【大化小,根据标准明确自己位置】 | O(nlogn) | O(n²) | O(nlogn) | 不稳定 |
归并排序 | 分治法,两个有序组合并后是有序【小到大在于合】 | O(nlogn) | O(nlogn) | O(n) | 稳定 |
堆排序 | 利用最大堆,依次弹出到末尾 【堆的性质】 | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
希尔排序 | 分组的插入排序【缩小增量排序】 | O(n^1.3) | O(1) | 不稳定 | |
计数排序 | 数作为数组的下标 【空间换时间】一个数一个桶 | O(n) | 稳定 | ||
桶排序 | 计数排序改进,一段范围一个桶 | ||||
基数排序 | 计数排序改进,一个键值数字一个桶 |
平均 快速排序效率最高 但是最坏下不如堆排序和归并排序
A. 冒泡排序(你行你上,不行别xxx)
重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。重复地进行直到没有再需要交换。大数会像“泡泡”一样慢慢的浮到后面
图解
分阶段状态
loop: 0 list: [5, 6, 8, 2, 7, 3, 4, 1, **9**]
loop: 1 list: [5, 6, 2, 7, 3, 4, 1, **8, 9**]
loop: 2 list: [5, 2, 6, 3, 4, 1, **7, 8, 9**]
loop: 3 list: [2, 5, 3, 4, 1, **6, 7, 8, 9**]
loop: 4 list: [2, 3, 4, 1, **5, 6, 7, 8, 9**]
loop: 5 list: [2, 3, 1, **4, 5, 6, 7, 8, 9**]
loop: 6 list: [2, 1, **3, 4, 5, 6, 7, 8, 9**]
loop: 7 list: [1, **2, 3, 4, 5, 6, 7, 8, 9**]
Python语言实现
def BubbleSort(list):
for i in range(len(list)-1): # 外圈两两对比要对比n-1次
for j in range(len(list)-i-1): # 两两对比
if list[j]>list[j+1]: # 顺序不对则交换
list[j],list[j+1] = list[j+1],list[j]
return list
Golang 语言实现
func BubbleSort(list []int) []int {
for i := 0; i < len(list)-1; i++ { // 外循环n-1次
for j := 0; j < len(list)-1-i; j++ {
if list[j] > list[j+1] { // 两两对比
list[j], list[j+1] = list[j+1], list[j] // 交换
}
}
}
return list
}
B. 选择排序 (最菜的前面去)
在未排序的序列中找出最小(大)元素与第一个位置的元素交换位置。
图解
分阶段状态
loop: 0 list: [**1**, 9, 6, 8, 2, 7, 3, 4, 5]
loop: 1 list: [**1, 2**, 6, 8, 9, 7, 3, 4, 5]
loop: 2 list: [**1, 2, 3**, 8, 9, 7, 6, 4, 5]
loop: 3 list: [**1, 2, 3, 4**, 9, 7, 6, 8, 5]
loop: 4 list: [**1, 2, 3, 4, 5**, 7, 6, 8, 9]
loop: 5 list: [**1, 2, 3, 4, 5, 6,** 7, 8, 9]
loop: 6 list: [**1, 2, 3, 4, 5, 6, 7,** 8, 9]
loop: 7 list: [**1, 2, 3, 4, 5, 6, 7, 8**, 9]
Python语言实现
def SelectionSort(list):
for i in range(len(list)-1): # 外圈两两对比要对比n-1次
minIndex=i
for j in range(i+1,len(list)): # 记住最小一个的位置
if list[j]<list[minIndex]:
minIndex=j
list[i],list[minIndex] = list[minIndex],list[i] #交换
return list
Golang语言实现
func SelectionSort(list []int) []int {
for i := 0; i < len(list)-1; i++ { // 外循环n-1次
minIndex := i
for j := i + 1; j < len(list); j++ { // 找出最小的
if list[minIndex] > list[j] {
minIndex = j
}
}
list[minIndex], list[i] = list[i], list[minIndex] //交换
}
return list
}
C. 插入排序(扑克牌理牌)
通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
图解
分阶段状态
loop: 1 list: [5, **9**, 6, 8, 2, 7, 3, 4, 1]
loop: 2 list: [5, **6**, 9, 8, 2, 7, 3, 4, 1]
loop: 3 list: [5, 6, **8**, 9, 2, 7, 3, 4, 1]
loop: 4 list: [2, **5**, 6, 8, 9, 7, 3, 4, 1]
loop: 5 list: [2, 5, 6, **7**, 8, 9, 3, 4, 1]
loop: 6 list: [2, **3**, 5, 6, 7, 8, 9, 4, 1]
loop: 7 list: [2, 3, **4**, 5, 6, 7, 8, 9, 1]
loop: 8 list: [**1**, 2, 3, 4, 5, 6, 7, 8, 9]
Python语言实现
def InsertionSort(list):
for i in range(1,len(list)): # 外圈未排序n-1次
j=i
peakOne = list[i]
while peakOne<list[j-1] and j>0: # 寻找插入位置并后移
list[j] = list[j-1]
j-=1
list[j]=peakOne # 插入
return list
Golang语言实现
func InsertionSort(list []int) []int {
for i := 1; i < len(list); i++ { // 外循环n-1次
j := i
peakValue := list[i]
for j > 0 && peakValue < list[j-1] { //寻找插入位置并后移
list[j] = list[j-1]
j--
}
list[j] = peakValue //插入
}
return list
}
D. 快速排序 (大化小,按照基准站位)
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
图解
Python语言实现
def QuickSort(list):
def swap(list,i,j):
list[i],list[j]=list[j],list[i]
def partition(list,left,right): # 分治法 划分子串
base = list[left] # 基准取左边第一个
swapIndex = left+1 # 用于交换的位置
for i in range(left,right+1): # 遍历列表 找到小的放在前面
if list[i]<base:
swap(list,swapIndex,i)
swapIndex += 1
swap(list,left,swapIndex-1) # 将基准插入中间
return swapIndex-1
def quicksort(list,left,right): # 递归函数
if left >= right: return # 退出条件
mid=partition(list,left,right) # 自己先排序一次
quicksort(list,left,mid-1) # 排序左边
quicksort(list,mid+1,right) # 排序右边
quicksort(list,0,len(list)-1) # 调用递归函数
return list
Golang 语言实现
func swap(list []int, i, j int) {
list[i], list[j] = list[j], list[i]
}
func partion(list []int, left, right int) int {
base := list[left] // 以最左边一个为基准
sindex := left + 1 // 交换插入的位置
for i := left; i <= right; i++ { // 遍历一遍 将小的转换到前面
if list[i] < base {
swap(list, sindex, i)
sindex++
}
}
swap(list, left, sindex-1) //将基准插入中间
return sindex - 1
}
func quicksort(list []int, left, right int) { // 递归函数
if left >= right {
return // 退出条件
}
mid := partion(list, left, right) // 划分成两个
quicksort(list, left, mid-1) // 排序左边
quicksort(list, mid+1, right) // 排序右边
}
func QuickSort(list []int) []int {
quicksort(list, 0, len(list)-1) //开始递归
return list
}
E.归并排序 (小并大,保持有序)
- 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
- 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
- 重复步骤 3 直到某一指针达到序列尾;
- 将另一序列剩下的所有元素直接复制到合并序列尾。
归并排序vs快速排序
- 归并排序:重在合,将数列递归分解成只有一个元素。核心的算法是合并函数 merge:将两个有序数组合并后仍然有序。merge 函数决定了归并排序的空间复杂度和稳定性。
- 快读排序:重在分,任意选择一个元素作为分区占,分为小于,等于,大于三部分,然后依次对小于和大于部分递归排序。核心的算法是分区函数 partition:将数列分为左中右三部分。partition 函数同样决定了快速排序的空间复杂度和稳定性。
图解
分阶段状态
[5] + [9] ---> [5, 9]
[6] + [8] ---> [6, 8]
[5, 9] + [6, 8] ---> [5, 6, 8, 9]
[2] + [7] ---> [2, 7]
[4] + [1] ---> [1, 4]
[3] + [1, 4] ---> [1, 3, 4]
[2, 7] + [1, 3, 4] ---> [1, 2, 3, 4, 7]
[5, 6, 8, 9] + [1, 2, 3, 4, 7] ---> [1, 2, 3, 4, 5, 6, 7, 8, 9]
Python语言实现
def merge(left,right): # 合并左右两个
result=[]
while len(left)>0 and len(right)>0: # 按照大小插入
if left[0]<right[0]:
result.append(left.pop(0))
else:
result.append(right.pop(0))
while len(left)>0:
result.append(left.pop(0))
while len(right)>0:
result.append(right.pop(0))
return result
def MergeSort(list): # 递归函数
if len(list)<2:
return list # 退出条件
import math
mid = math.floor(len(list)/2) # 分割点
left = list[0:mid]
right = list[mid:]
return merge(MergeSort(left), MergeSort(right))
Golang语言实现
func merge(left, right []int) []int {
result := make([]int, len(left)+len(right)) //缓存数组
mergeIndex := 0
leftIndex := 0
rightIndex := 0
//按照大小插入
for leftIndex < len(left) && rightIndex < len(right) {
if left[leftIndex] < right[rightIndex] {
result[mergeIndex] = left[leftIndex]
leftIndex++
mergeIndex++
} else {
result[mergeIndex] = right[rightIndex]
rightIndex++
mergeIndex++
}
}
for leftIndex < len(left) {
result[mergeIndex] = left[leftIndex]
leftIndex++
mergeIndex++
}
for rightIndex < len(right) {
result[mergeIndex] = right[rightIndex]
rightIndex++
mergeIndex++
}
return result
}
func MergeSort(list []int) []int {
if len(list) < 2 {
return list // 退出条件
}
var mid int = len(list) / 2 // 划分为一半
return merge(MergeSort(list[0:mid]), MergeSort(list[mid:]))
}
F. 堆排序(堆的性质)
- 借助堆来实现的选择排序
- 建堆——>将根节点与最后一个交换 输出——>重建
- 升序用大顶堆
- 降序用小顶堆
图解
分阶段状态
init: [9, 8, 7, 5, 2, 6, 3, 4, 1]
loop: 8 [8, 5, 7, 4, 2, 6, 3, 1, **9**]
loop: 7 [7, 5, 6, 4, 2, 1, 3, **8, 9**]
loop: 6 [6, 5, 3, 4, 2, 1, **7, 8, 9**]
loop: 5 [5, 4, 3, 1, 2, **6, 7, 8, 9**]
loop: 4 [4, 2, 3, 1, **5, 6, 7, 8, 9**]
loop: 3 [3, 2, 1, **4, 5, 6, 7, 8, 9**]
loop: 2 [2, 1,** 3, 4, 5, 6, 7, 8, 9**]
loop: 1 [1, **2, 3, 4, 5, 6, 7, 8, 9**]
Python 语言实现
def HeapSort(list):
def down(list,i,len):
while 2*i+1<=len:
l=2*i+1
r=2*i+2 if 2*i+2<len else len
maxChild=max(list[l],list[r]) # 最小的孩子
if maxChild<list[i]: # 已经满足最大堆性质
return
k = l if list[l]==maxChild else r # 最大孩子位置
list[k],list[i]=list[i],list[k] # 交换父子节点
i=k
#1. 建立初最大堆
for i in range(math.ceil(len(list)-1/2),-1,-1): # 将每个父节点下沉
down(list,i,len(list)-1)
#2. 依次取顶点放入到后面
for j in range(len(list)-1,0,-1):
list[j],list[0] =list[0],list[j] # 交换首尾
down(list,0,j-1)
print("loop:",j," ", list)
return list
Golang实现
// 节点下沉
func heapDown(list []int, i, len int) {
for 2*i+1 <= len {
left := 2*i + 1 // 左右子节点
right := 2*i + 2
if right > len {
right = len // 注意溢出
}
var maxIndex int // 寻找最大的子节点
if list[left] > list[right] {
maxIndex = left
} else {
maxIndex = right
}
if list[maxIndex] < list[i] {
return //已经满足最大堆性质
} else {
list[i], list[maxIndex] = list[maxIndex], list[i]
i = maxIndex
}
}
}
func HeapSort(list []int) []int {
//1. 建立初始的最大堆
for i := len(list) / 2; i >= 0; i-- { //最后一个父节点开始
heapDown(list, i, len(list)-1)
}
//2. 依次取堆顶到后面
for i := len(list) - 1; i > 0; i-- {
list[i], list[0] = list[0], list[i] //放在最后
heapDown(list, 0, i-1) //调整堆
}
return list
}
G. 希尔排序 (插入排序改进版)
- 也叫缩小增量排序
- 分割成若干子序列进行直接插入排序 相隔某个增量
- 基本有序后对整体进行插入排序
图解
分阶段状态
gap: 4 [**1**, 7, 3, 4, **2**, 9, 6, 8, **5**]
gap: 2 [1, 4, 2, 7, 3, 8, 5, 9, 6]
gap: 1 [1, 2, 3, 4, 5, 6, 7, 8, 9]
Python语言实现
def ShellSort(list):
#希尔插入
def shellInsert(list,s,gap):
for i in range(s,len(list),gap):
temp = list[i]
j=i-gap
while j>=0 and list[j]>temp: #以gap为间隔插入
list[j],list[j+gap]=list[j+gap],list[j]
j-=gap
list[j+gap]=temp
gap=int(math.floor(len(list)/2)) #逐步缩小gap
while gap>0:
for i in range(gap):
shellInsert(list,i,gap)
gap=int(gap/2)
return list
Golang语言实现
// 希尔插入 以gap为间隔,i为起点
func shellInsert(list []int, i, gap int) {
for ; i < len(list); i += gap {
temp := list[i]
j := i - gap
for j >= 0 && temp < list[j] {
swap(list, j, j+gap)
j -= gap
}
list[j+gap] = temp
}
}
// 希尔排序
func ShellSort(list []int) []int {
for gap := len(list) / 2; gap > 0; gap /= 2 {
for i := 0; i < gap; i++ {
shellInsert(list, i, gap)
}
}
return list
}
*//一次排序 增量为d*voidShellInsert(int* a, int d,int len){
int i;
for(int i=d;i<len-1;i++){
int j=i-d;
int temp=a[i];*//移动的量*while(j>=0&&a[j]>temp){
a[j+d]=a[j];*//后移 重复*
j-=d;
}
if(j!=i-d){
a[j+d]=temp;*//交换*
}
}
}
*//希尔排序*voidShellSort(int* a,int len){
int d=len/2;*//一开始 间隔为len/2**//间隔缩小*while(d>=1){
ShellInsert(a,d,len);
d/=2;
}
}
H. 计数排序 (空间换时间)
-
O(n)
-
前提条件 带排序的数满足一定的范围
-
需要空间
-
数作为数组的下标
图解
Python语言实现
def CountSort(list):
maxValue=max(list) # 最大的数
Counter =[0 for _ in range(maxValue+1) ] # 计数器
for i in list: # 遍历一次计数
Counter[i]+=1
result=[] # 根据计数输出
for i in range(len(Counter)):
for j in range(Counter[i]):
result.append(i)
return result
Golang语言实现
// 计数排序
func CountSort(list []int) []int {
// 获得最大值
max := list[0]
for i := range list {
if list[i] > max {
max = list[i]
}
}
// 容器
counter := make([]int, max+1)
// 遍历一次统计
for i := range list {
value := list[i]
counter[value]++
}
// 取出排序
index := 0
for i := range counter {
for j := 0; j < counter[i]; j++ {
list[index] = i
index++
}
}
return list
}
拓展:桶排序
桶排序是计数排序的扩展版本,计数排序可以看成每个桶只存储相同元素,而桶排序每个桶存储一定范围的元素,通过映射函数,将待排序数组中的元素映射到各个对应的桶中,对每个桶中的元素进行排序,最后将非空桶中的元素逐个放入原序列中。
拓展:基数排序
将整数按位数切割成不同的数字,然后按每个位数分别比较。