详解经典排序算法 | 青训营笔记

89 阅读1分钟

在这一节课中,主要介绍了三个常见的算法。\

1. 插入排序(Insertion Sort)

image.png

将元素不断插入已经排序好的array中

  1. 起始只有一个元素5,其本身是一个有序序列
  2. 后续元素插入有序序列中,即不断交换,直到找到第一个比其小的元素
func InsertionSort(array []int) []int {
	if len(array) <= 1 {
		return array
	}
	for i := 1; i < len(array); i++ {
		value := array[i]
		j := i - 1
		for ; j >= 0; j-- {
			if value < array[j] {
				array[j+1] = array[j]
			} else {
				break
			}
		}
		array[j+1] = value
	}
	return array
}
 
func main() {
	array := []int{39, 2, 5, 23, 54, 12, 78, 34, 45, 40}
	array = InsertionSort(array)
	for i, v := range array {
		fmt.Printf("下标为%d的值为%d", i, v)
		fmt.Println()
	}
}

核心思想:取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空时,算法结束。

2. 快速排序(Quick Sort)

v2-c411339b79f92499dcb7b5f304c826f4_b.gif 分治思想,不断分割序列直到序列整体有序

  1. 选定一个pivot (轴点)
  2. 使用pivot分割序列,分成元素比 pivot大和元素比 pivot 小两个序列
package main

import "fmt"

var List = []int{8, 5, 9, 3, 10, 2}

func main() {
	fmt.Println("Before Sort: ",List)
	quick_sort(List, 0, len(List)-1)
	fmt.Println("After Sort: ",List)
}

func quick_sort(nums []int, low, high int) {
	if low < high {
		partition_pos := partition(nums, low, high)
		quick_sort(nums, low, partition_pos - 1)
		quick_sort(nums, partition_pos + 1, high)
	}
}

func partition(nums []int, low, high int) int {
	pivotKey := nums[low]
	for ;low < high; {
		// 先从high开始找,再从low开始
		for ; low < high && nums[high] >= pivotKey; {
			high--
		}
		// 因为关键字已经赋值给关键字,所以high记录可以和low交换
		nums[low] = nums[high]
		for ; low < high && nums[low] <= pivotKey; {
			low++
		}
		// 同理进行交换
		nums[high] = nums[low]
	}
	// 当low和high相等时,已完成一次排序,把关键字插入到中间
	nums[low] = pivotKey
	return low
}

3. 堆排序(Heap Sort)

1258817-20190420150936225-1441021270.gif

利用堆的性质形成的排序算法

  1. 构造一个大顶堆
  2. 将根节点(最大元素)交换到最后一个位置,调整整个堆,如此反复
package main

import "fmt"

func HeapSort(arr []int) []int {
	length := len(arr)
	for i := 0; i < length; i++ {
		lastmesslen := length - i
		HeapScortMax(arr, lastmesslen)
		//fmt.Println(arr)
		if i < length {
			arr[0], arr[lastmesslen-1] = arr[lastmesslen-1], arr[0]
		}
		//fmt.Println("ex", arr)
	}
	return arr
}

func HeapScortMax(arr []int, length int) []int {
	//length := len(arr)
	if length <= 1 {
		return arr
	} else {
		depth := length/2 - 1 //节点深度,n,2*n+1,2*n+2
		for i := depth; i >= 0; i-- {
			topmax := i
			//left right 代表非叶子节点
			left := 2*i + 1
			right := 2*i + 2
			if left <= length-1 && arr[left] > arr[topmax] { //防止越界
				topmax = left
			}
			if right <= length-1 && arr[right] > arr[topmax] {
				topmax = right
			}

			if topmax != i {
				arr[i], arr[topmax] = arr[topmax], arr[i]
			}
		}
		return arr
	}
}

func main() {
	arr := []int{15, 21, 0, 23, 8, -1}
	fmt.Print(HeapSort(arr))
}

4.三种算法性能比较

image.png

  1. 插入排序平均和最坏情况时间复杂度都是O(n^2),性能不好快速排序整体性能处于中间层次
  2. 堆排序性能稳定

接下来在不同情况下进行比较

1.随机序列 image.png

  • 插入排序在短序列中速度最快;
  • 快速排序在其他情况中速度最快;
  • 堆排序速度与最快算法差距不大。

2.有序序列 image.png 插入排序在序列已经有序的情况下最快

结论 image.png

  • 所有短序列和元素有序情况下,插入排序性能最好
  • 在大部分的情况下,快速排序有较好的综合性能
  • 几乎在任何情况下,堆排序的表现都比较稳定