有些时候我们也希望得到有序链表,那么还得排序。
链表不能象数组那样随机访问,分割要多费周章。不过由于链表的归并不需要额外空间,归并排序可以做到O(1)级的空间开销。
func mergeSort[T constraints.Ordered](list *ll.Node[T]) (head, tail *ll.Node[T]) {
head, tail = list, ll.FakeHead(&head)
size := 0
for ; list != nil; size += 2 { //先探规模
if list.Next == nil {
tail = list
size++
break
}
a, b := list, list.Next
list = b.Next
if a.Val > b.Val {
tail.Next, b.Next, a.Next = b, a, list
tail = a
} else {
tail = b
} }
for step := 2; step < size; step *= 2 {
list, tail = head, ll.FakeHead(&head)
for list != nil {
lst1 := list
lst2 := cut(lst1, step) //切至多step大小的一段
list = cut(lst2, step) //切第二段
var node *ll.Node[T]
tail.Next, node = merge(lst1, lst2) //归并之
tail, node.Next = node, list
} }
return head, tail
}
因为先分割后归并,归并排序在每轮处理中需要遍历两次链表,在速度上稍逊于快速排序。
随机三点取中法在链表中难以实现,链表上的快速排序更容易陷入最坏情况。使用内省排序比较合理。
func introSort[T constraints.Ordered](list *ll.Node[T], life uint) (head, tail *ll.Node[T]) {
head, tail = sort2(list)
if head == nil {
if life == 0 {
head, tail = mergeSort(list) //召唤小伙伴
} else {
lst1, pivot, lst2, _ := partition(list)
head, tail = introSort(lst1, life-1)
tail.Next = pivot //前后衔接
pivot.Next, tail = introSort(lst2, life-1)
} }
return head, tail
}
和归并排序的情况类似,基数排序在链表上的实现比在数组上的实现较为容易,也无需太多的额外的空间。
func RadixSort[T constraints.Integer](head *ll.Node[T]) *ll.Node[T] {
//...
const bk = 1 << 8
parts, tails := new([bk]*ll.Node[T]), new([bk]*ll.Node[T])
for step := uint(0); step < bitWidth; step += 8 {
for i := 0; i < bk; i++ {
parts[i] = nil
tails[i] = ll.FakeHead(&parts[i])
}
for node := head; node != nil; node = node.Next {
radix := uint8(node.Val >> step)
tails[radix].Next, tails[radix] = node, node
}
tail := ll.FakeHead(&head)
for i := 0; i < bk; i++ {
if parts[i] != nil {
tail.Next = parts[i]
tail = tails[i]
} }
tail.Next = nil
}
//...
return head
}
通过简单的评测,我们可以看出链表上排序性能比数组上的要差不少:
Benchmark_MergeSort-6 3267426 741.1 ns/op
Benchmark_QuickSort-6 4010521 549.4 ns/op
Benchmark_IntroSort-6 4172546 537.5 ns/op
Benchmark_RadixSort-6 4724077 488.3 ns/op
Benchmark_DescMergeSort-6 16931956 90.29 ns/op
Benchmark_DescQuickSort-6 296650 119665 ns/op
Benchmark_DescIntroSort-6 7884870 164.8 ns/op
Benchmark_DescRadixSort-6 11793471 142.9 ns/op
ok DSGO/linkedlist/sort
Benchmark_MergeSort-6 14570674 88.89 ns/op
Benchmark_QuickSort-6 16693329 84.88 ns/op
Benchmark_IntroSort-6 16923432 85.57 ns/op
Benchmark_RadixSort-6 58933306 25.33 ns/op
Benchmark_DescMergeSort-6 58247337 26.43 ns/op
Benchmark_DescQuickSort-6 100000000 13.41 ns/op
Benchmark_DescIntroSort-6 100000000 15.42 ns/op
Benchmark_DescRadixSort-6 22972530 50.76 ns/op
ok DSGO/array/sort
相对连续结构,链式结构在访问效率上存在劣势。即使在不需要随机访问的场合,链表还是无法完全取代数组。