算法:golang实现并发快速排序

理论

从“单线程快速排序” 中我们可以看到。快排可以分为三个步骤

  1. 将当前待排序序列分割为三个部分:低于基准的,等于基准的,高于基准的
  2. 然后分别对 低于基准的序列 排序、高于基准的序列 排序,一直重复1、2两个步骤,知道不能再次分割[也就是只剩下一个元素或者两个元素]
  3. 将三个部分合并为一个,那就是有序的了

单线程

在这里插入图片描述
在这里插入图片描述

多线程

版本1

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

func QuickSort(arr []int)[]int{
	length := len(arr)
	if(length <= 1){
		return arr;
	}else{
		var wg sync.WaitGroup
		rand.Seed(time.Now().UnixNano())
		base := arr[rand.Intn(length)] // 随机基准
		low := make([]int, 0)
		mid := make([]int, 0)
		high := make([]int, 0)

		for i := 0; i < length ; i++ {
			if arr[i] < base {
				low = append(low, arr[i])
			}else if arr[i] == base {
				mid = append(mid, arr[i])
			}else{
				high = append(high, arr[i])
			}
		}

		wg.Add(2)
		go func() {
			low = QuickSort(low)
			wg.Done()
		}()
		go func() {
			high = QuickSort(high)
			wg.Done()
		}()

		wg.Wait()

		return append(append(low, mid...), high...)
	}
}

func main() {

	data := []int{3, 6, 23, 7, 2, 4, 9, 13}
	fmt.Println(QuickSort(data))
}

在这里插入图片描述
从上面我们可以看出,虽然我们可以对 low和high快排并行开始,但是我们必须要等待它们切割完成之后才能合并,那有没有改进的方法呢?----- 我们可以使用管道来存储切割结果。

版本2

简化版

假设待排序序列为[3, 1, 2],选取第一个元素作为基准序列
在这里插入图片描述
比起版本1。虽然版本2在合并的时候,也是要先合并完low,然后合并mid,然后合并high。但是在合并low的时候可以一边排序一边合并
在这里插入图片描述

package main

import (
	"fmt"
	"math/rand"
	"time"
)

// 没有必要返回chan,
func QuickSort(arr []int, channel chan int){
	length := len(arr)

	if(length <= 0){
		close(channel)
		return ;
	}else if(length <= 1){
		channel <- arr[0]
		close(channel)
		return
	}else{
		low := make([]int, 0)
		mid := make([]int, 0)
		high := make([]int, 0)

		rand.Seed(time.Now().Unix())
		base := arr[rand.Intn(length)]

		for i := 0; i < length ; i++ {
			if arr[i] < base {
				low = append(low, arr[i])
			}else if arr[i] == base {
				mid = append(mid, arr[i])
			}else{
				high = append(high, arr[i])
			}
		}


		left_chan := make(chan int)
		go func() {
			QuickSort(low, left_chan);
		}()

		right_chan := make(chan int)
		go func() {
			QuickSort(high, right_chan)
		}()

		for v := range left_chan {
			channel <- v
		}

		for _, v := range mid {
			channel <- v
		}

		for v := range right_chan {
			channel <- v
		}

		close(channel)
		return
	}
}

func main() {
	data := []int{3, 6, 23, 7, 2, 4, 9, 13}
	channel := make(chan int)
	go QuickSort(data, channel)
	for v := range channel {
		fmt.Println(v)
	}
}

优化:从上面可以看到,每调用一层快排,线程数量*2,如果要排序的数组长度很长,因为管道会一直阻塞,所以线程会一直等待它的子线程们调用到最底层才会结束,也就是说,如果排序序列很长,那么运行着的线程会是很恐怖的大,因此这个地方需要优化下

package main

import (
	"fmt"
	"math/rand"
	"time"
)

/**
 * 作用:并发排序
 * 参数:
 *   arr 待排序数组
 *   channel: 结果管道
 *   level:   当前层级
 *	 threads: 线程总数
 * 返回值: 无
*/
func QuickSort(arr []int, channel chan int, level int, threads int){
	level = level * 2;//每加深一个级,多*2个线程
	length := len(arr)

	if(length <= 0){
		close(channel)
		return ;
	}else if(length <= 1){
		channel <- arr[0]
		close(channel)
		return
	}else{
		low := make([]int, 0)
		mid := make([]int, 0)
		high := make([]int, 0)

		rand.Seed(time.Now().Unix())
		base := arr[rand.Intn(length)]

		for i := 0; i < length ; i++ {
			if arr[i] < base {
				low = append(low, arr[i])
			}else if arr[i] == base {
				mid = append(mid, arr[i])
			}else{
				high = append(high, arr[i])
			}
		}


		left_chan := make(chan int, len(low))  // 注意这里必修加buffer。 否则会死锁。。为什么????
		right_chan := make(chan int, len(high))

		if(level <= threads){//如果线程超过执行数量,顺序调用,否则并发调用
			go QuickSort(low, left_chan, level, threads);
			go QuickSort(high, right_chan, level, threads)
		}else{
			QuickSort(low, left_chan, level, threads);
			QuickSort(high, right_chan, level, threads)
		}

		for v := range left_chan {
			channel <- v
		}

		for _, v := range mid {
			channel <- v
		}

		for v := range right_chan {
			channel <- v
		}

		close(channel)
		return
	}
}


func main() {
	data := []int{3, 6, 23, 7, 2, 4, 9, 13}
	channel := make(chan int)
	go QuickSort(data, channel, 1, 10)  // 这里必须是go,否则会一直阻塞,导致死锁
	for v := range channel {
		fmt.Println(v)
	}
}




  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值