快速排序归并排序
即使思想简单,但实现依然十分不易。主要原因大概是因为大多采用数组进行实现,为了降低内存开销,会复用已有的数组,从而使空间复杂度降至 O ( 1 ) O(1) O(1)。为了达到这一目的,人们采用一些奇技淫巧,让代码理解、实现都变容易出错。
易于理解
本文不再赘述这两种算法的思想和基于数组的实现方式。仅提供基于链表的实现方式的介绍。
链表结构链表结点结构(以整数型为例)如下。
type ListNode struct {
data int // 链表结点的数据
next *ListNode // 下一个结点的指针
}
type List *ListNode
快速排序
首元结点
然后从node开即往后遍历,如果某个结点的值比pivot中的值小,则把该节点挂在sublist链表中。否则挂在pivot链表中。如下图,3因为比7小,所以挂在sublist,8因为比7大所有挂在pivot链表。
遍历完成后,分别对这个两子链表进行排序。直接递归调用即可。排序后如下图所示。
最后把这两个子链表合并即可
平均时间复杂度: O ( n ⋅ l o g ( n ) ) O(n\cdot log(n)) O(n⋅log(n))
空间复杂度: O ( 1 ) O(1) O(1)
代码如下:
func QuickSort(list List) {
// 如果链表没有元素或只有一个元素,那么不用排序
if list.next == nil || list.next.next == nil {
return
}
// 指定第一个元素为pivot, 第二个元素为node
node := list.next.next
pivot := list.next
pivot.next = nil
sublist := list
sublist.next = nil
// 把其它元素根据大小分别放在pivot的左边和右边。
// 原链表将拆分成以sublist和pivot为头结点的两个链表
for node != nil {
nodeNext := node.next
if node.data < pivot.data {
node.next = sublist.next
sublist.next = node
} else {
node.next = pivot.next
pivot.next = node
}
node = nodeNext
}
// 递归对两个子链表进行排序
QuickSort(sublist)
QuickSort(pivot)
// 合并两个链表
i := list
for i.next != nil {
i = i.next
}
i.next = pivot
}
归并排序
在链表上实现归并排序关键在于如何快速地找到分割点将链表分割成两个子链表。本文提供的方法只需要扫描链表一遍即可确定分割点(如代码所示)。
算法首先找到分割点midNode, 该结点及其之前的结点被分在list1中,之后的结点被分在list2中。
之后递归地对这两个子链表进行排序。如下图。
最后,把排序后的list1和list2归并即可。归并过程如下,详细如图:
- 归并过程首先会把list1和list2指向两个子链表的第一个节点。把list链表置空。
- 然后,依次摘取两个子链表中具有较小数值的结点放入list链中。
- 最后得到的list就是归并完成的结果。
平均时间复杂度: O ( n ⋅ l o g ( n ) ) O(n\cdot log(n)) O(n⋅log(n))
最好和最坏性况时间复杂度: O ( n ⋅ l o g ( n ) ) O(n\cdot log(n)) O(n⋅log(n))
空间复杂度: O ( 1 ) O(1) O(1)
func MergeSort(list List) {
// 如果链表没有元素或只有一个元素,那么不用排序
if list.next == nil || list.next.next == nil {
return
}
// 计算链表长度, 并找到分割点
midNode := list
shouldNext := true
for i := list.next; i != nil; i = i.next {
if shouldNext {
midNode = midNode.next
}
shouldNext = !shouldNext
}
// 把链表分成两个子链表
list1 := list
list2 := &ListNode{next: midNode.next}
midNode.next = nil
// 分别对两个子链表递归处理
MergeSort(list1)
MergeSort(list2)
// 归并且合并两个子链表
list1 = list1.next
list2 = list2.next
tail := list
tail.next = nil
for list1 != nil && list2 != nil {
if list1.data < list2.data {
tail.next = list1
list1 = list1.next
tail = tail.next
} else {
tail.next = list2
list2 = list2.next
tail = tail.next
}
}
for list1 != nil {
tail.next = list1
list1 = list1.next
tail = tail.next
}
for list2 != nil {
tail.next = list2
list2 = list2.next
tail = tail.next
}
}
测试
本文把两个算法排序的结果与go语言自带的排序功能进行比较,验证了算法实现的正确性。
func main() {
list1 := new(ListNode)
list2 := new(ListNode)
array := make([]int, 100)
// 随机生成100个数
rand.Seed(time.Now().UnixMicro())
for i := 0; i < 100; i++ {
number := rand.Intn(100)
list1.next = &ListNode{data: number, next: list1.next}
list2.next = &ListNode{data: number, next: list2.next}
array[i] = number
}
QuickSort(list1)
MergeSort(list2)
sort.Ints(array)
// 输出, 并验证排序是否正确
i1 := list1.next
i2 := list2.next
j := 0
for i1 != nil {
if i1.data != array[j] {
panic("list1 not equal")
}
if i2.data != array[j] {
panic("list2 not equal")
}
fmt.Print(i1.data, ",")
i1 = i1.next
i2 = i2.next
j++
}
fmt.Println()
}
测试结果: