冒泡排序算法的思想就是,第一个位置上的元素先和第二个位置上的元素比较,如果大于,则交换两个元素,第一个位置上的元素再和第三个位置上的元素比较,如果大于,再交换位置,依次类推,比较完最后一个元素则第一个位置便是最小的元素。然后第二个位置上的元素再依次比较。重复这一过程直到所有位置上的元素都选定。“冒泡”的名字很形象的表现出这种排序的特点。
1.冒泡排序
package main
import "fmt"
var sortArray = []int{41, 24, 76, 11, 45, 64, 21, 69, 19, 36}
//冒泡排序
func main() {
pop()
fmt.Println(sortArray)
}
func pop() {
length := len(sortArray)
for i := 0; i < length - 1; i++ {
for j := 0; j < length -1 -i; j++ {
if sortArray[j] > sortArray[j+1] {
swap(j, j+1)
}
}
}
}
// Swap the position of a and b
func swap(a, b int) {
sortArray[a], sortArray[b] = sortArray[b], sortArray[a]
}
冒泡排序的时间复杂度尽管是n^2,但其排序速度还是非常慢,不像插入排序,冒泡排序每个元素都要与其后所有的元素比较,这无疑让冒泡排序速度非常的慢。
2.快速排序法
快速排序的优点在于其平均复杂度(the average case)比较好,可以达到nlogn,而且具有很小的常数因子。那么这里就会有疑问,一般情况我们都说最差情况下(the worst case)的复杂度,而不是平均情况下的复杂度,这样我们就知道该算法最差能达到多快。的确,最差情况的时间复杂度很重要,但往往情况都是随机的,一般情况显然要远远多于最差的情况,平均复杂度比较好的算法具有很大的优势。其实,很多优秀的算法正是因为其拥有比较好的平均复杂度。
package main
import (
"fmt"
)
const MAX = 10
var sortArray = []int{41, 24, 76, 11, 45, 64, 21, 69, 19, 36}
//快速排序算法
//1.选取最后一个数,从头到尾将所有数与最后一个数进行对比,若比最后一个数小则与第i个数交换,(i从-1开始,每遇到一个数+1)
//2.交换完一遍后,将第i+1个数与最后一个数进行交换
//3.重新开始第一步,但是分成了两部分,以第i个数为分界线,分别对左右两边进行第一步和第二步,直到left >= right为止退出
func main() {
fmt.Println("before sort:")
show()
quickSort(sortArray, 0, MAX-1)
fmt.Println("after sort:")
show()
}
func quickSort(sortArray []int, left, right int) {
if left < right {
pos := partition(sortArray, left, right)
quickSort(sortArray, left, pos-1)
quickSort(sortArray, pos+1, right)
}
}
func partition(sortArray []int, left, right int) int {
key := sortArray[right]
i := left - 1
for j := left; j < right; j++ {
if sortArray[j] <= key {
i++
swap(i, j)
}
}
swap(i+1, right)
return i + 1
}
// Swap the position of a and b
func swap(a, b int) {
sortArray[a], sortArray[b] = sortArray[b], sortArray[a]
}
// foreach
func show() {
for _, value := range sortArray {
fmt.Printf("%d\t", value)
}
}
我们可以看到快速排序的平均复杂度是趋近与nlogn
3.归并排序
/**
时间复杂度:
- 最坏情况下:O(n(log(n)))
- 平均情况下:O(n(log(n)))
- 最好情况下:O(n(log(n)))
空间复杂度:
- 最坏情况下:O(n)
*/
func main() {
var data = []int{7, 6, 1, 5, 3, 4, 2, 8}
result := mergeSort(data)
fmt.Println(result)
}
//log(n)
func mergeSort(data []int) []int {
var n = len(data)
if n == 1 {
return data
}
middle := n / 2
left := make([]int, middle)
right := make([]int, n-middle)
for i := 0; i < n; i++ {
if i < middle {
left[i] = data[i]
} else {
right[i-middle] = data[i]
}
}
return merge(mergeSort(left), mergeSort(right))
}
//n
func merge(left, right []int) []int {
result := make([]int, len(left)+len(right))
var i = 0
for len(left) > 0 && len(right) > 0 {
if left[0] <= right[0] {
result[i] = left[0]
left = left[1:]
i ++
} else {
result[i] = right[0]
right = right[1:]
i ++
}
}
for j := 0; j < len(left); j++ {
result[i] = left[j]
i ++
}
for j := 0; j < len(right); j ++ {
result[i] = right[j]
i ++
}
//fmt.Println(result)
return result
}
4.插入排序
/**
插入排序是简单的就地 O(n²) 排序算法。同样,它在大型列表中效率较低,但它具有很少有的优点:
- 自适应:时间复杂度随着已经基本排序的列表而减少 – 如果每个元素不超过其最终排序位置的 k 个位置,则 O(nk)
- 稳定:相等值的索引的相对位置不变
- 就地:只需要一个常数 O(1) 的额外的内存空间
- 在实践中比泡沫或选择排序更有效
时间复杂度:
- 最坏情况下:O(n²)
- 平均情况下:O(n²)
- 最好情况下:O(n)
空间复杂度:
- 最坏情况下:O(1)
*/
/**
1.从第一个元素开始,该元素可以认为已经被排序
2.取出下一个元素,在已经排序的元素序列中从后向前扫描
3.如果该元素(已排序)大于新元素,将该元素移到下一位置
4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5.将新元素插入到该位置后
6.重复步骤2~5
*/
func main() {
var data = []int{7, 6, 1, 5, 3, 4, 2, 8}
insertSort(data)
fmt.Println(data)
}
func insertSort(data []int) {
length := len(data)
for i := 1; i < length ; i ++ {
for j := i; j > 0 ; j -- {
if data[j] > data[j - 1] {
data[j], data[j - 1] = data[j - 1], data[j]
}
}
}
}