前言
最近在学习Go语言,于是用go实现了两种排序的并发实现,下面我将一一进行介绍。
快排实现
下面先贴出快排代码:
func QuickSort(num []int, low, high int) {
if low >= high {
return
}
i, j := low, high
key := num[low]
for i < j {
for j > i && num[j] >= key {
j--
}
num[i] = num[j]
for i < j && num[i] < key {
i++
}
num[j] = num[i]
}
num[j] = key
QuickSort(num, low, i-1)
QuickSort(num, i+1, high)
}
接着我们先贴出并发代码,再进行一些讨论
import "sync"
func MutiQuickSort(num []int){
//如果缺少该句,由于下面第一次调用QuickSort,没有加一操作,执行完后直接lock.Done(),将导致数量减为-1而报错
lock.Add(1)
QuickSort(num, 0, len(num)-1)
lock.Wait()
}
var lock sync.WaitGroup
func QuickSort(num []int, low, high int) {
defer lock.Done()
if low >= high {
return
}
i, j := low, high
key := num[low]
for i < j {
for j > i && num[j] >= key {
j--
}
num[i] = num[j]
for i < j && num[i] < key {
i++
}
num[j] = num[i]
}
num[j] = key
lock.Add(2)
go QuickSort(num, low, i-1)
go QuickSort(num, i+1, high)
}
并发快排代码的思考
(1)为什么使用WaitGroup来实现?
因为快速排序对数组不断进行分割,最终使其完全有序。这里在考虑并发实现时,只需对其每次分割后的两部分
调用go启动协程进行实现,而这种并发情况下,主线程只需等待所有的排序携程都执行完成,即可实现并发排序。
[可参考下面的归并排序进行对比]
(2)关于WaitGroup的使用?
//定义sync.WaitGroup变量(全局变量)
var Lock sync.WaitGroup
//主线程等待(等待数量减为0,主线程结束)
Lock.Wait()
//子协程开启时(数量加2)
Lock.Add(2)
//子协程完成时(数量减1)
Lock.Done()
归并实现
我们仍然先来贴归并代码
func MergeSort(a []int, left, right int) {
if left < right {
mid := left + (right-left)/2
MergeSort(a, left, mid)
MergeSort(a, mid+1, right)
Merge(a, left, mid, right)
}
}
func Merge(a []int, left, mid, right int) {
arr := make([]int, 0)
i, j := left, mid+1
for i <= mid && j <= right {
if a[i] <= a[j] {
arr = append(arr,a[i])
i++
} else {
arr = append(arr,a[j])
j++
}
}
arr = append(arr, a[i:mid+1]...)
arr = append(arr, a[j:right+1]...)
for i, v := range arr {
a[left+i] = v
}
}
接着是并发归并的代码
func MutiMergeSort(a []int) {
ch := make(chan int, 1)
defer close(ch)
MergeSort(a, 0, len(a)-1, ch)
}
func MergeSort(a []int, left, right int, c chan int) {
if left < right {
ch := make(chan int, 2)
defer close(ch)
mid := left + (right-left)/2
go MergeSort(a, left, mid, ch)
go MergeSort(a, mid+1, right, ch)
<-ch
<-ch
Merge(a, left, mid, right)
}
c <- 1
}
func Merge(a []int, left, mid, right int) {
arr := make([]int, 0)
i, j := left, mid+1
for i <= mid && j <= right {
if a[i] <= a[j] {
arr = append(arr, a[i])
i++
} else {
arr = append(arr, a[j])
j++
}
}
arr = append(arr, a[i:mid+1]...)
arr = append(arr, a[j:right+1]...)
for i, v := range arr {
a[left+i] = v
}
}
思考
(1)这里为什么使用channel来实现?
首先,我们还是来分析归并排序。归并排序在将数组进行分割成单个元素之后,还要不断合并已经排好有序的数组段。
所以在合并时,就需要等待 执行这两个数组段排序的协程都完成,方可继续。否则,会出现错误。
(2)channel的使用?
//创建缓冲区大小为1024的int类型的channel
c := make(chan int, 1024)
//写入数据
c <- 1
//读出数据
ch := <- c
测试代码
Go本身提供了一套轻量级的测试框架。符合规则的测试代码会在运行测试时被自动识别并执行。
命名规则:以 “_test”结尾的go文件会被看作测试程序
import (
"testing"
"fmt"
"math/rand"
)
func TestMergeSort(t *testing.T) {
list := make([]int, 0)
for i := 0; i < 1000000; i++ {
v := rand.Int() % 100000
list = append(list, v)
}
MutiMergeSort(list)
for _, v := range list {
fmt.Print(v, " ")
}
}