前言:在使用排序算法时,我发现对于数组和切片类型的数据,我能够非常熟练地使用快速排序。然而,当涉及到链表排序时,我只能实现最基本的冒泡排序和快速排序。但相比之下,我认为归并排序在处理链表时效率更高。因此,我希望通过这次回顾,更系统地掌握数组和链表的不同排序方式,尤其是归并排序的应用。并且,在最后,我会介绍一下我实际运用切片时遇到的问题。
快速排序
(图像来源:https://www.cnblogs.com/onepixel/p/7674659.html)
func q_sort(nums []int) {
if len(nums) <= 1 {
return
}
left, right := 0, len(nums)-1
pivot := nums[len(nums)/2]
for left <= right {
for left <= right && nums[left] < pivot {
left++
}
for left <= right && nums[right] > pivot {
right--
}
if left <= right {
nums[left], nums[right] = nums[right], nums[left]
left++
right--
}
}
if right > 0 {
q_sort(nums[:right+1])
}
if left < len(nums) {
q_sort(nums[left:])
}
}
归并排序
(图像来源:https://www.cnblogs.com/onepixel/p/7674659.html)
(图像来源:https://www.hello-algo.com/chapter_sorting/merge_sort/)
func mergeSort(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return head
}
mid := findMiddle(head)
right := mid.Next
mid.Next = nil
l := mergeSort(head)
r := mergeSort(right)
return merge(l, r)
}
func findMiddle(head *ListNode) *ListNode {
slow, fast := head, head
var prev *ListNode
for fast != nil && fast.Next != nil {
prev = slow
slow = slow.Next
fast = fast.Next.Next
}
return prev
}
func merge(l1, l2 *ListNode) *ListNode {
if l1 == nil {
return l2
}
if l2 == nil {
return l1
}
if l1.Val < l2.Val {
l1.Next = merge(l1.Next, l2)
return l1
} else {
l2.Next = merge(l1, l2.Next)
return l2
}
}
案例:力扣算法题 148. 排序链表
代码示例
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func sortList(head *ListNode) *ListNode {
if head==nil||head.Next==nil{
return head
}
mid:=findMid(head)
right:=mid.Next
mid.Next=nil
l:=sortList(head)
r:=sortList(right)
return merge(l,r)
}
func findMid(head *ListNode)*ListNode{
slow,fast:=head,head
var prev *ListNode
/*
for fast!=nil&&fast.Next!=nil
这里注意,我第一次不小心写为,fast.Next!=nil&&fast!=nil
这一种写法是错的,因为在遍历的过程 fast 是在前面的,可能出现fast==nil的情况了
所以 fast!=nil && fast.Next!=nil ,就可以通过短路机制,不再走后边的条件了
*/
for fast!=nil&&fast.Next!=nil{
prev=slow
slow=slow.Next
fast=fast.Next.Next
}
return prev
}
func merge(l1, l2 *ListNode)*ListNode{
if l1==nil{
return l2
}
if l2==nil{
return l1
}
if l1.Val<l2.Val{
l1.Next=merge(l1.Next,l2)
return l1
}else{
l2.Next=merge(l1,l2.Next)
return l2
}
}
关于切片做参数进行append时遇到的bug
问:请写出一下程序运行的答案?
func main() {
doAppend := func(s []int) {
s = append(s, 1)
printSliceStruct(s)
}
s := make([]int, 8, 8)
doAppend(s[:4])
printSliceStruct(s)
doAppend(s)
printSliceStruct(s)
}
func printSliceStruct(s []int) {
fmt.Println(s)
fmt.Printf("len=%d cap=%d\n", len(s), cap(s))
}
这里主要就是思考,切片做参数传递的时候,传递的是对底层数组的引用还是因为append增加数据导致扩容,而分配新的内存空间了。
以及以下实际中遇到的问题:
这是因为在闭包中引用了外部的变量task,go1.22之后,在for range中避免了task复用同一个地址变量,但是闭包会捕获并引用task这个变量,所以为了避免循环引用task,就进行重新赋值。
而且for range也不能遍历string类型的变量。