| 算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 稳定性 | |||
|---|---|---|---|---|---|---|---|---|
| 冒泡排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 | |||
| 选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 | |||
| 插入排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 | |||
| 希尔排序 | O(nlog2n) | O(n1.3) | O(n2) | O(1) | 不稳定 | |||
| 归并排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(n) | 稳定 | |||
| 快速排序 | O(nlog2n) | O(nlog2n) | O(n2) | O(log2n) | 不稳定 | |||
| 堆排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(1) | 不稳定 | |||
| 计数排序 | O(n+k) | O(n+k) | O(n+k) | O(k) | 稳定 | |||
| 桶排序 | O(n+k) | O(n+k) | O(n2) | O(n+k) | 稳定 | |||
| 基数排序 | O(n*k) | O(n*k) | O(n*k) | O(n+k) | 稳定 |
冒泡排序(bubbleSort)
1. 思想
- 比较相邻元素,如果前者比后者大,则进行位置交换
- 从第一个开始比较到未确定位置的最后一个,每经过一轮,则代表当前最大的数到达本次最后的位置
2.复杂度分析
时间复杂度
- 平均:O(n2)
- 最好:如果数组本来就是有序的,则经过一轮比较即可,共需要比较n-1次,时间复杂度是 O(n)
- 最坏:如果是倒序,则需要比较 n-1 + n-2 + n-3 +...+1=n(n-1)/2次,为O(n2) 空间复杂度 使用常数个辅助单元O(1)
稳定性 稳定,因为 i>j时,A[i]=A[j],不会进行交换
3.代码实现
package main
import(
"fmt"
)
func bubbleSort(array []int ){
//最后一次交换的位置
lastExchangeIndex := 0;
//无序数组的边界
sortBorder := len(array) - 1
for i := 0; i < len(array)-1; i++ {
//当前数组是否为有序的标记,初始值是 true
isSorted := true
for j := 0; j < sortBorder; j++ {
//相等时不用交换,所以冒泡排序是稳定的
if array[j]>array[j+1] {
array[j],array[j+1]=array[j+1],array[j]
//发生了交换,所以当前数组还不能确定是有序的
isSorted = false
//记录当前交换的位置
lastExchangeIndex = j;
}
}
sortBorder = lastExchangeIndex
// 此轮没有发生元素的交换,证明是有序的
if isSorted{
break;
}
}
}
func main(){
array:=[]int{5,8,6,3,9,2,1,7}
bubbleSort(array)
fmt.Println(array)
}
选择排序(selectionSort)
1.思想
每一次选出最小者直接交换到左侧,省出了多余的元素交换
2.复杂度分析
时间复杂度 平均:O(n2) 最坏:O(n2)
空间复杂度 O(1)
稳定性 不稳定。当数列包含多个值相等的元素时,选择排序有可能打乱它们的原有顺序。
3.代码实现
package main
import(
"fmt"
)
func selectionSort(array []int){
for i := 0; i < len(array)-1; i++ {
//设置当前数组最小的值的索引
minIndex := i
for j := i+1; j < len(array); j++ {
if array[j]<array[minIndex]{
//更新索引为当前最小值的索引
minIndex = j
}
}
//把最小值放到本次循环的第一位
array[i],array[minIndex]=array[minIndex],array[i]
}
}
func main(){
array:=[]int{5,8,6,3,9,2,1,7}
selectionSort(array)
fmt.Println(array)
}
插入排序(InsertionSort)
1.思想
维护一个有序区,把元素一个一个插入到有序区的适当位置,直到所有元素有序为止
2.复杂度分析
时间复杂度 O(n2)
空间复杂度 O(1)
稳定性 稳定
3.代码实现
package main
import(
"fmt"
)
func InsertionSort(array []int){
//设array[0] 为有序数组
for i := 1; i < len(array); i++ {
//插入数值
insertValue:=array[i]
j:=i-1
for ;j>=0&&array[j]>insertValue; j-- {
//如果前面的值大于插入值,依次把值向后移动
array[j+1]=array[j]
}
//进行赋值
array[j+1]=insertValue
}
}
func main(){
array:=[]int{5,8,6,3,9,2,1,7}
InsertionSort(array)
fmt.Println(array)
}
希尔排序
1.思想
设置希尔增量每次折半,逐步分组进行粗调,最后进行插入排序
2.复杂度分析
时间复杂度
- 平均:O(nlogn)
- 最坏:O(n2) + 检查的时间
空间复杂度 O(1)
稳定性 不稳定
3.代码实现
package main
import(
"fmt"
)
func ShellSort(array []int){
n:=len(array)
for gap:=n/2;gap>0;gap=gap/2{
//使用希尔增量的方式,即每次折半
for i := gap; i < n; i++ {
temp:=array[i]
j:=i-gap
for j >=0&&array[j]>temp{
array[j+gap]=array[j]
j=j-gap
}
array[j+gap] = temp
}
}
}
func main(){
array:=[]int{5,3,9,12,6,1,7,2,13,4,11,8,10}
ShellSort(array)
fmt.Println(array)
}
快速排序(Quick Sort)
1. 思想 (分治)
快速排序是从冒泡元素演变而来。在快速排序中,元素的比较和交换是从两端向中间进行的,较大的元素一轮就能交换到后面的位置,而较小的元素一轮就能交换到前面的位置,元素每次移动的距离较远,所以比较次数和移动次数较少,速度较快。
- 在待排序的元素任取一个元素作为基准(通常选第一个元素,但最好的方法是从待排序元素中随机选取一个为基准),称为基准元素(pivot)
- 将待排序的元素进行分区,比基准元素大的元素放在它的右边,比基准元素小的放在它的左边
- 对左右两个分区重复以上步骤,直到所有的元素都是有序的
2.复杂度分析
时间复杂度
- 平均:O(nlogn)
- 最坏:O(n2) 空间复杂度
- 平均:O(logn)
- 最坏:O(n) 稳定性 不稳定,因为基准元素的比较和交换是跳跃进行的
3.代码实现
挖坑法 把基准元素的位置当做一个坑,然后按照右->左->右->左的指针顺序,依次进行比较和填坑。
package main
import(
"fmt"
)
func quickSort(array []int,startIndex,endIndex int){
//递归结束条件:startIndex >= endIndex 时
if startIndex>endIndex{
return
}
//得到基准元素的位置
pivotIndex:=partition(array,startIndex,endIndex)
//用分治发递归基准元素的前面和后面两个部分
quickSort(array,startIndex,pivotIndex-1)
quickSort(array,pivotIndex+1,endIndex)
}
func partition(array []int,startIndex,endIndex int)int{
//取第一个元素作为基准元素
pivot:=array[startIndex]
left,right:=startIndex,endIndex
//记录坑的位置,初始值为基准值的位置
index:=startIndex
//在左右指针重合 或者 交错的时候结束
for right>=left{
//right指针从右向左进行比较
for right>=left {
if array[right]<pivot{
array[left]=array[right]
index = right
left++
break
}
right--
}
//left指针从左向右进行比较
for right>=left{
if array[left]>pivot{
array[right] = array[left]
index = left
right--
break
}
left++
}
}
array[index] = pivot
return index
}
func main(){
array:=[]int{4,7,6,5,3,2,8,1}
quickSort(array,0,len(array)-1)
fmt.Println(array)
}
指针交换法
package main
import(
"fmt"
)
func quickSort(array []int,startIndex,endIndex int){
//递归结束条件:startIndex >= endIndex 时
if startIndex>endIndex{
return
}
//得到基准元素的位置
pivotIndex:=partition(array,startIndex,endIndex)
//用分治发递归基准元素的前面和后面两个部分
quickSort(array,startIndex,pivotIndex-1)
quickSort(array,pivotIndex+1,endIndex)
}
func partition(array []int,startIndex,endIndex int)int{
//取第一个元素作为基准元素
pivot:=array[startIndex]
left,right:=startIndex,endIndex
//在左右指针重合 或者 交错的时候结束
for right!=left{
//right指针从右向左进行比较
for left<right && array[right]>pivot{
right--
}
//left指针从左向右进行比较
for left<right && array[left]<=pivot{
left++
}
//交换 left 和 right 指向的元素
if left<right{
array[left],array[right]=array[right],array[left]
}
}
//pivot和指针重合点交换
array[left],array[startIndex] = array[startIndex],array[left]
return left
}
func main(){
array:=[]int{4,7,6,5,3,2,8,1}
quickSort(array,0,len(array)-1)
fmt.Println(array)
}
归并排序
1.思想
最小分组比较,依次合并
- 创建一个大集合,长度为两个小集合之和
- 从左到右逐一比较两个小集合中的元素,把较小的优先放入大集合
2.复杂度分析
时间复杂度 O(nlogn) 空间复杂度 O(n)
3.代码实现
package main
import(
"fmt"
)
func mergeSort(array []int,start,end int){
if start<end{
//0 1 2 3 4 5 6 7
//5,8,6,3,9,2,1,7
mid:=(start+end)/2
//拆成两个小集合,分别进行递归
mergeSort(array,start,mid)
mergeSort(array,mid+1,end)
//把两个有序的小集合,归并成一个大集合
merge(array,start,mid,end)
}
}
func merge(array []int,start,mid,end int){
//开辟大集合
tempArray:=make([]int,end-start+1)
//设置指针
p1 := start
p2 := mid +1
p :=0
//比较两个小集合的元素,依次放入大集合
for p1<=mid && p2<=end {
if array[p1]<=array[p2]{
tempArray[p]=array[p1]
p1++
}else{
tempArray[p]=array[p2]
p2++
}
p++
}
//如果左侧还有剩余,依次放入尾部
for p1<=mid{
tempArray[p]=array[p1]
p++
p1++
}
//如果右侧还有剩余,依次放入尾部
for p2<=end {
tempArray[p]=array[p2]
p++
p2++
}
//把大集合的元素复制回原数组
for i := 0; i < len(tempArray); i++ {
array[i+start]=tempArray[i]
}
}
func main(){
array:=[]int{5,8,6,3,9,2,1,7}
mergeSort(array,0,len(array)-1)
fmt.Println(array)
}
堆排序
讲解视频1.思想
本质是完全二叉树,根节点为最大值或者最小值
2.复杂度分析
时间复杂度 O(nlogn)
空间复杂度 O(n)
3.代码实现
二叉堆的构建
package main
import(
"fmt"
)
//插入(上浮)
func upAdjust(array []int){
childIndex := len(array)-1
parentIndex := (childIndex-1)/2
//保存插入叶子节点值,用于最后的赋值
temp:=array[childIndex]
for childIndex>0 && temp < array[parentIndex]{
//单项赋值,不用交换
array[childIndex] = array[parentIndex]
childIndex = parentIndex
parentIndex = (childIndex-1)/2
}
array[childIndex] = temp
}
//删除(下沉)
func downAdjust(array []int,parentIndex,length int){
//保存父节点的值,用于最后的赋值
temp:= array[parentIndex]
childIndex:=2*parentIndex+1
for childIndex<length{
//如果存在右孩子,并且右孩子小于左孩子的值,定位到右孩子
if childIndex+1<length && array[childIndex+1]<array[childIndex] {
childIndex++
}
//如果父节点小于任何一个孩子的值,直接退出
if temp<=array[childIndex]{
break
}
array[parentIndex] = array[childIndex]
parentIndex = childIndex
childIndex = 2*childIndex+1
}
array[parentIndex] = temp
}
//构建最小二叉堆
func buildHeap(array []int ){
for i := (len(array)-2)/2; i >=0; i-- {
downAdjust(array,i,len(array))
}
}
func main(){
array:=[]int{1,3,2,6,5,7,8,9,10,0}
upAdjust(array)
fmt.Println(array)
array = []int{7,1,3,10,5,2,8,9,6}
buildHeap(array)
fmt.Println(array)
}
堆排序
//待补充
桶排序
1.思想
桶排序就是把数值按照范围进行划分,把数值依次放入一个个划分的范围内,称之为 桶,然后在桶内进行排序,然后依次输出每个桶的值。
2.复杂度分析
时间复杂度 O(n+k)
空间复杂度 O(n+k)
稳定
3.代码实现
package main
import (
"fmt"
"sort"
)
func bucketSort(array []int){
//1.找到最小值和最大值
min,max:=1000000,0
for _,v :=range array{
if(v>max){
max = v
}
if(v<min){
min =v
}
}
//2.初始化桶
bucketNum := (max-min)/len(array)+1
var bucketList [][]int
for i := 0; i < bucketNum; i++ {
bucketItem := make([]int,0)
bucketList = append(bucketList,bucketItem)
}
//3.把元素放到桶内
for _,v:=range array{
number:= (v-min)/len(array)
bucketList[number]=append(bucketList[number],v)
}
//4.桶内进行排序
for _,v := range bucketList{
sort.Ints(v)
}
///5.依次输出
index:=0
for _,v:=range bucketList{
for _,m:=range v{
array[index] = m
index++
}
}
}
func main(){
array:=[]int{5,16,95,1,45,66,75,20,62,38,76,94,43}
bucketSort(array)
fmt.Println(array)
}