排序稳定性
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的
意义
有时候我们需要按照多个条件排序,比如举重比赛,按照成绩排序,成绩相同,按照体重的逆序排序。
那么如果排序算法是稳定的,我们可以先按照体重逆序排序后再按照成绩排序,则结果就是我们要的。如果是不稳定排序,我们需要额外的步骤保证结果的正确。
快速排序
QuickSort
Golang实现
package main
import "fmt"
func main() {
nums := []int{7, 2, 6, 4, 1, 9, 4}
QuickSort(nums)
fmt.Print(nums)
}
func QuickSort(nums []int) {
n := len(nums)
if n <= 1 {
return
}
key := nums[0]
i := 0
j := n - 1
for i != j {
for ; j >= i; j-- {
if nums[j] <= key {
nums[i], nums[j] = nums[j], nums[i]
break
}
}
for ; i < j; i++ {
if nums[i] > key {
nums[i], nums[j] = nums[j], nums[i]
break
}
}
}
QuickSort(nums[:i])
if i < n {
QuickSort(nums[:i+1])
}
}
要点
算法思想:找出一个数key,大于key的放右边,小于key的放左边。如何放?使用交换的办法:
即 key = nums[0], 然后 i = 0, j = n-1
先进行j的循环,判断nums[j]<key,真则nums[j],nums[i]交换,假则 j-- 直到真break
再进行i的循环,判断nums[i]>key,真则nums[i],nums[j]交换,假则i++直到真break
若i==j,则不再进行上面两个步骤,否则执行上面两个步骤
剩下的部分,左边部分 QuickSort(left) ,右边的部分 QuickSort(right)
归并排序
和QuickSort 快速排序 非常像
但是 QuickSort 会把左右两边排列一下,但是归并排序不做这个操作
图片来源:https://www.cnblogs.com/piperck/p/6030122.html
归并排序就是把表不断二分,直到二分到最小单元(一个数)
然后切成很多最小的块之后,再拼接起来
拼接的方式是,排序拼接
A, B 两个表
选取A中元素a, 选取B中元素b,作比较把小的放入C
最后返回C
golang实现
func mergeLists(left *ListNode, right *ListNode) *ListNode {
result := &ListNode{0, nil}
p := left
q := right
c := result
for p != nil || q != nil {
if (p != nil && q != nil && p.Val < q.Val) || (p != nil && q == nil) {
c.Next = p
p = p.Next
c = c.Next
} else {
c.Next = q
q = q.Next
c = c.Next
}
}
return result.Next
}
func sortList(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return head
}
// find the split point
slow := head
fast := head
for fast != nil && fast.Next != nil {
fast = fast.Next.Next
if fast != nil && fast.Next != nil {
slow = slow.Next
}
}
right := sortList(slow.Next)
slow.Next = nil
left := sortList(head)
return mergeLists(left, right)
}
堆排序
建立一个堆,比如小顶堆
即每个节点,都比其子节点小
堆怎么建立起来的?
n = len(nums)-1
通过root=n/2 然后子树是root2+1、root2+2
然后对(root, root2+1, root2+2) 这棵子树做调整
首先child 等于 root2+1, root2+2较小的子树
然后child和root作比较,要是child比较小,则root,child数据交换
然后root 等于 child, 再此进行调整
通过转移root+for循环来持续调整
func heapBuild(nums []int, k int) {
for root := (k - 1) / 2; root >= 0; root-- {
minheap(root, k, nums)
}
}
func heapSort(nums []int, k int) {
for end := k - 1; end >= 0; end-- {
if nums[0] < nums[end] {
nums[0], nums[end] = nums[end], nums[0]
minheap(0, end-1, nums)
}
}
}
func minheap(root int, end int, nums []int) {
for {
child := root*2 + 1
if child >= end {
break
}
if child+1 < end && nums[child+1] < nums[child] { // 是小于 不是小于等于
child = child + 1
}
if nums[child] < nums[root] {
nums[root], nums[child] = nums[child], nums[root]
root = child
} else {
break
}
}
}
通过上面的代码就建立了小顶堆
然后对小顶堆进行排序
因为小顶堆的堆顶也就是 nums[0] 一定是最小的
那么对 1 ~ n 进行建立小顶堆,建立完成后,nums[1]是剩下的数的堆顶,是最小的,然后对 2 ~ n 进行建立小顶堆…
这就是升序的排法
降序就是反过来,代码实现:
func heapSort(nums []int, k int) {
for end := k - 1; end >= 0; end-- {
if nums[0] < nums[end] {
nums[0], nums[end] = nums[end], nums[0]
minheap(0, end-1, nums)
}
}
}