Go中的惯用快速排序

huangapple go评论180阅读模式
英文:

Idiomatic quicksort in Go

问题

我正在研究Go,并试图寻找经典算法的惯用实现,以便对这种语言有所了解。

我选择了快速排序,因为我对数组与切片、原地排序与复制排序很感兴趣。在我弄清楚一些概念之后,我想编写一个并行实现。

有人可以展示一下Go中的快速排序的惯用实现吗?

英文:

I'm taking a look at Go, and was trying to find idiomatic implementations of classic algorithms to get a feel for the language.

I chose quicksort because I'm particularly interested in the arrays vs slices, in-place vs copy deal. After I settle some concepts down, I want to write a parallel impl.

Can someone please show me an idiomatic implementation of quicksort in Go?

答案1

得分: 37

func qsort(a []int) []int {
if len(a) < 2 { return a }

left, right := 0, len(a) - 1

// 选择一个基准点
pivotIndex := rand.Int() % len(a)

// 将基准点移到右边
a[pivotIndex], a[right] = a[right], a[pivotIndex]

// 将比基准点小的元素堆放在左边
for i := range a {
if a[i] < a[right] {
a[i], a[left] = a[left], a[i]
left++
}
}

// 将基准点放在最后一个较小元素之后
a[left], a[right] = a[right], a[left]

// 递归调用
qsort(a[:left])
qsort(a[left + 1:])

return a
}

英文:

Well, I ended up with this. I don't know enough Go to say it's idiomatic, but I used slices, one-line swaps and a range clause. It's been pretty informative for me to write, so I thought I should share.

func qsort(a []int) []int {
  if len(a) &lt; 2 { return a }

  left, right := 0, len(a) - 1

  // Pick a pivot
  pivotIndex := rand.Int() % len(a)

  // Move the pivot to the right
  a[pivotIndex], a[right] = a[right], a[pivotIndex]

  // Pile elements smaller than the pivot on the left
  for i := range a {
    if a[i] &lt; a[right] {
      a[i], a[left] = a[left], a[i]
      left++
    }
  }

  // Place the pivot after the last smaller element
  a[left], a[right] = a[right], a[left]

  // Go down the rabbit hole
  qsort(a[:left])
  qsort(a[left + 1:])


  return a
}

答案2

得分: 4

看一下标准库中的sort包的源代码,特别是sort.Sort函数。

英文:

Take a look at the source of the sort package from the standard library, particularily sort.Sort.

答案3

得分: 1

只返回翻译好的部分:

仅仅将代码从一种语言(例如C)简单地转换为另一种语言(例如Go),很少会产生习惯用法的代码。

这个注释是一个提示,实现递归解决方案可能不是最佳策略。Go使用短栈。

这是一个迭代的快速排序解决方案。

package main

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

type Item int
type Items []Item

// 算法与数据结构,N. Wirth
// http://www.ethoberon.ethz.ch/WirthPubl/AD.pdf
// 2.3.3 Partition Sort, Quicksort, NonRecursiveQuickSort.
func qSort(a Items) {
	const M = 12
	var i, j, l, r int
	var x Item
	var low, high = make([]int, 0, M), make([]int, 0, M)

	low, high = append(low, 0), append(high, len(a)-1)
	for { // (*从栈中取出顶部请求*)
		l, low = low[len(low)-1], low[:len(low)-1]
		r, high = high[len(high)-1], high[:len(high)-1]
		for { // (*对a[l] ... a[r]进行分区*)
			i, j = l, r
			x = a[l+(r-l)/2]
			for {
				for ; a[i] < x; i++ {
				}
				for ; x < a[j]; j-- {
				}
				if i <= j {
					a[i], a[j] = a[j], a[i]
					i++
					j--
				}
				if i > j {
					break
				}
			}
			if i < r { // (*将右分区的请求压入栈*)
				low, high = append(low, i), append(high, r)
			}
			r = j // (*现在l和r界定了左分区*)
			if l >= r {
				break
			}
		}
		if len(low)+len(high) == 0 {
			break
		}
	}
}

func main() {
	nItems := 4096
	a := make(Items, nItems)
	rand.Seed(time.Now().UnixNano())
	for i := range a {
		a[i] = Item(rand.Int31())
	}
	qSort(a)
	for i := range a[1:] {
		if a[i] > a[i+1] {
			fmt.Println("(* 排序错误 *)")
		}
	}
}

显然,还有更多工作要做。例如,改进分区算法并更改qsort函数的签名以使用Go的interface类型。

英文:

Simply taking code from one language, for example C, and simplistically converting it to another language, for example Go, rarely produces idiomatic code.

> Go sort package -- sort.c source file
>
> func quickSort(data Interface, a, b, maxDepth int) {
> // . . .
> // Avoiding recursion on the larger subproblem guarantees
> // a stack depth of at most lg(b-a).
> // . . .
> }

This comment is a clue that implementing a recursive solution may not be the best strategy. Go uses short stacks.

Here's an iterative Quicksort solution.

package main

import (
	&quot;fmt&quot;
	&quot;math/rand&quot;
	&quot;time&quot;
)

type Item int
type Items []Item

// Algorithms and Data Structures, N. Wirth
// http://www.ethoberon.ethz.ch/WirthPubl/AD.pdf
// 2.3.3 Partition Sort, Quicksort, NonRecursiveQuickSort.
func qSort(a Items) {
	const M = 12
	var i, j, l, r int
	var x Item
	var low, high = make([]int, 0, M), make([]int, 0, M)

	low, high = append(low, 0), append(high, len(a)-1)
	for { // (*take top request from stack*)
		l, low = low[len(low)-1], low[:len(low)-1]
		r, high = high[len(high)-1], high[:len(high)-1]
		for { // (*partition a[l] ... a[r]*)
			i, j = l, r
			x = a[l+(r-l)/2]
			for {
				for ; a[i] &lt; x; i++ {
				}
				for ; x &lt; a[j]; j-- {
				}
				if i &lt;= j {
					a[i], a[j] = a[j], a[i]
					i++
					j--
				}
				if i &gt; j {
					break
				}
			}
			if i &lt; r { // (*stack request to sort right partition*)
				low, high = append(low, i), append(high, r)
			}
			r = j // (*now l and r delimit the left partition*)
			if l &gt;= r {
				break
			}
		}
		if len(low)+len(high) == 0 {
			break
		}
	}
}

func main() {
	nItems := 4096
	a := make(Items, nItems)
	rand.Seed(time.Now().UnixNano())
	for i := range a {
		a[i] = Item(rand.Int31())
	}
	qSort(a)
	for i := range a[1:] {
		if a[i] &gt; a[i+1] {
			fmt.Println(&quot;(* sort error *)&quot;)
		}
	}
}

Clearly, there is more to be done. For example, improving the partitioning algorithm and changing the qsort function signature to use a Go interface type.

答案4

得分: 1

我现在只是学习go,并决定实现qsort作为一个简单的任务,使用通道和并行性,因为在qsort中,在对数组进行枢轴划分后,可以同时对两个子数组进行排序。我最终得到了以下代码:

package main

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

func qsort_pass(arr []int, done chan int) []int{
    if len(arr) < 2 {
        done <- len(arr)
        return arr
    }
    pivot := arr[0]
    i, j := 1, len(arr)-1
    for i != j {
        for arr[i] < pivot && i!=j{
            i++
        }
        for arr[j] >= pivot && i!=j{
            j--
        }
        if arr[i] > arr[j] {
            arr[i], arr[j] = arr[j], arr[i]
        }
    }
    if arr[j] >= pivot {
        j--
    }
    arr[0], arr[j] = arr[j], arr[0]
    done <- 1;

    go qsort_pass(arr[:j], done)
    go qsort_pass(arr[j+1:], done)
    return arr
}

func qsort(arr []int) []int {
    done := make(chan int)
    defer func() {
        close(done)
    }()

    go qsort_pass(arr[:], done)

    rslt := len(arr)
    for rslt > 0 {
        rslt -= <-done;
    }
    return arr
}

func main() {
    fmt.Println("About to sort.")
    rand.Seed(time.Now().UTC().UnixNano())
    arr_rand := make([]int, 20)
    for i := range arr_rand {
        arr_rand[i] = rand.Intn(10)
    }
    fmt.Println(arr_rand)
    qsort(arr_rand)
    fmt.Println(arr_rand)
}

这里有很多可以改进的地方,比如使用go协程池而不是为每个分区生成一个新的go协程,并且如果len(arr)足够小,可以使用插入排序进行排序,或者使用其他类型而不是[]int。但总体上看起来是一个不错的起点。

英文:

I'm just learning go right now and decided to implement qsort as a simple task, using channels and parallelism since in qsort you can sort both halves concurrently after pivoting the array. I ended up with smth like that:

package main

import (
	&quot;fmt&quot;
    &quot;math/rand&quot;
    &quot;time&quot;
)

func qsort_pass(arr []int, done chan int) []int{
    if len(arr) &lt; 2 {
        done &lt;- len(arr)
        return arr
    }
    pivot := arr[0]
    i, j := 1, len(arr)-1
    for i != j {
        for arr[i] &lt; pivot &amp;&amp; i!=j{
            i++
        }
        for arr[j] &gt;= pivot &amp;&amp; i!=j{
            j--
        }
        if arr[i] &gt; arr[j] {
            arr[i], arr[j] = arr[j], arr[i]
        }
    }
    if arr[j] &gt;= pivot {
        j--
    }
    arr[0], arr[j] = arr[j], arr[0]
    done &lt;- 1;

    go qsort_pass(arr[:j], done)
    go qsort_pass(arr[j+1:], done)
    return arr
}

func qsort(arr []int) []int {
    done := make(chan int)
    defer func() {
        close(done)
    }()

    go qsort_pass(arr[:], done)

    rslt := len(arr)
    for rslt &gt; 0 {
        rslt -= &lt;-done;
    }
    return arr
}

func main() {
	fmt.Println(&quot;About to sort.&quot;)
    rand.Seed(time.Now().UTC().UnixNano())
    arr_rand := make([]int, 20)
    for i := range arr_rand {
        arr_rand[i] = rand.Intn(10)
    }
    fmt.Println(arr_rand)
    qsort(arr_rand)
    fmt.Println(arr_rand)
}

There's plenty of room for improvement here like using a pool of go-routines instead of spawning a new go-routine for each partition, and sorting with insertion sort if len(arr) is small enough or using something other than []int. But generally it looks like a good place to start.

答案5

得分: 0

func quickSort(arr []int) []int {
sort(arr, 0, len(arr) - 1)

return arr

}

func sort(arr []int, low, high int) {
if low < high {
pi := partition(arr, low, high)
sort(arr, low, pi-1)
sort(arr, pi+1, high)
}
}

func partition(arr []int, low, high int) int {
pivot := arr[high]
i := low-1

for j := low; j < high; j++ {
	if arr[j] <= pivot {
		i++
		arr[i], arr[j] = arr[j], arr[i]
	}
}

arr[i+1], arr[high] = arr[high], arr[i+1]

return i+1

}

英文:

Could use some thoughts on this.

func quickSort(arr []int) []int {
	sort(arr, 0, len(arr) - 1)

	return arr
}

func sort(arr []int, low, high int) {
	if low &lt; high {
		pi := partition(arr, low, high)
		sort(arr, low, pi-1)
		sort(arr, pi+1, high)
	}
}

func partition(arr []int, low, high int) int {
	pivot := arr[high]
	i := low-1

	for j := low; j &lt; high; j++ {
		if arr[j] &lt;= pivot {
			i++
			arr[i], arr[j] = arr[j], arr[i]
		}
	}
	
	arr[i+1], arr[high] = arr[high], arr[i+1]

	return i+1
}

答案6

得分: 0

对于Go 1.18及以上版本,使用泛型:

import (
	"golang.org/x/exp/constraints"
)

func QuickSort[T constraints.Ordered](a []T) {
	if len(a) > 1 {
		quickSort(a, 0, len(a)-1)
	}
}

func quickSort[T constraints.Ordered](a []T, lo, hi int) {
	if lo < hi {
		p := partition(a, lo, hi)
		quickSort(a, lo, p-1)
		quickSort(a, p+1, hi)
	}
}

func partition[T constraints.Ordered](a []T, lo, hi int) int {
	l, p := lo, findPivot(a, lo, hi)
	for r := lo; r < hi; r++ {
		if a[r] < a[p] {
			a[l], a[r] = a[r], a[l]
			l++
		}
	}
	a[l], a[p] = a[p], a[l]
	return l
}

// 三数取中法
func findPivot[T constraints.Ordered](a []T, lo, hi int) int {
	mi := (lo + hi) / 2
	if a[lo] > a[mi] {
		a[lo], a[mi] = a[mi], a[lo]
	}
	if a[lo] > a[hi] {
		a[lo], a[hi] = a[hi], a[lo]
	}
	if a[mi] > a[hi] {
		a[mi], a[hi] = a[hi], a[mi]
	}
	a[mi], a[hi-1] = a[hi-1], a[mi]
	return hi - 1
}

用法:

a := []int{1, 8, 4, 9, 6, 3, 5, 2, 7, 0}
fmt.Println(a, "(原始)")
QuickSort(a)
fmt.Println(a, "(排序)")

b := []string{"tuv", "abc", "jkl", "ghi", "mno", "wxyz", "def", "pqrs"}
fmt.Println(b, "(原始)")
QuickSort(b)
fmt.Println(b, "(排序)")
英文:

For Go 1.18 and above, using generics:

import (
	&quot;golang.org/x/exp/constraints&quot;
)

func QuickSort[T constraints.Ordered](a []T) {
	if len(a) &gt; 1 {
		quickSort(a, 0, len(a)-1)
	}
}

func quickSort[T constraints.Ordered](a []T, lo, hi int) {
	if lo &lt; hi {
		p := partition(a, lo, hi)
		quickSort(a, lo, p-1)
		quickSort(a, p+1, hi)
	}
}

func partition[T constraints.Ordered](a []T, lo, hi int) int {
	l, p := lo, findPivot(a, lo, hi)
	for r := lo; r &lt; hi; r++ {
		if a[r] &lt; a

{ a[l], a[r] = a[r], a[l] l++ } } a[l], a

= a

, a[l] return l } // median of three technique func findPivot[T constraints.Ordered](a []T, lo, hi int) int { mi := (lo + hi) / 2 if a[lo] &gt; a[mi] { a[lo], a[mi] = a[mi], a[lo] } if a[lo] &gt; a[hi] { a[lo], a[hi] = a[hi], a[lo] } if a[mi] &gt; a[hi] { a[mi], a[hi] = a[hi], a[mi] } a[mi], a[hi-1] = a[hi-1], a[mi] return hi - 1 }

Usage:

a := []int{1, 8, 4, 9, 6, 3, 5, 2, 7, 0}
fmt.Println(a, &quot;(original)&quot;)
QuickSort(a)
fmt.Println(a, &quot;(sorted)&quot;)

b := []string{&quot;tuv&quot;, &quot;abc&quot;, &quot;jkl&quot;, &quot;ghi&quot;, &quot;mno&quot;, &quot;wxyz&quot;, &quot;def&quot;, &quot;pqrs&quot;}
fmt.Println(b, &quot;(original)&quot;)
QuickSort(b)
fmt.Println(b, &quot;(sorted)&quot;)

答案7

得分: 0

func QuickSortArr(arr []int) {
var i int
var j int
var hi int
var hSave bool
aLen := len(arr)
helpArr := make([]int, aLen)
hSave = true
var tmpHelp []int
var tmpArr []int

for m := 1; m < aLen; m += m {
	i = 0
	j = 0 + m
	hi = 0
	if hSave {
		tmpHelp = helpArr
		tmpArr = arr
	} else {
		tmpHelp = arr
		tmpArr = helpArr
	}
	for i < aLen && j < aLen {
		i2 := i + m
		j2 := j + m
		for i < i2 && i < aLen && j < j2 && j < aLen {
			if tmpArr[i] > tmpArr[j] {
				tmpHelp[hi] = tmpArr[j]
				hi++
				j++
			} else {
				tmpHelp[hi] = tmpArr[i]
				hi++
				i++
			}
		}
		for i < i2 && i < aLen {
			tmpHelp[hi] = tmpArr[i]
			hi++
			i++
		}
		for j < j2 && j < aLen {
			tmpHelp[hi] = tmpArr[j]
			hi++
			j++
		}

		i += m
		j += m
	}

	for i < aLen {
		tmpHelp[hi] = tmpArr[i]
		hi++
		i++
	}
	for j < aLen {
		tmpHelp[hi] = tmpArr[j]
		hi++
		j++
	}

	hSave = !hSave
}

if !hSave {
	copy(arr, helpArr)
}

}

英文:
func QuickSortArr(arr []int) {
	var i int
	var j int
	var hi int
	var hSave bool
	aLen := len(arr)
	helpArr := make([]int, aLen)
	hSave = true
	var tmpHelp []int
	var tmpArr []int

	for m := 1; m &lt; aLen; m += m {
		i = 0
		j = 0 + m
		hi = 0
		if hSave {
			tmpHelp = helpArr
			tmpArr = arr
		} else {
			tmpHelp = arr
			tmpArr = helpArr
		}
		for i &lt; aLen &amp;&amp; j &lt; aLen {
			i2 := i + m
			j2 := j + m
			for i &lt; i2 &amp;&amp; i &lt; aLen &amp;&amp; j &lt; j2 &amp;&amp; j &lt; aLen {
				if tmpArr[i] &gt; tmpArr[j] {
					tmpHelp[hi] = tmpArr[j]
					hi++
					j++
				} else {
					tmpHelp[hi] = tmpArr[i]
					hi++
					i++
				}
			}
			for i &lt; i2 &amp;&amp; i &lt; aLen {
				tmpHelp[hi] = tmpArr[i]
				hi++
				i++
			}
			for j &lt; j2 &amp;&amp; j &lt; aLen {
				tmpHelp[hi] = tmpArr[j]
				hi++
				j++
			}

			i += m
			j += m
		}

		for i &lt; aLen {
			tmpHelp[hi] = tmpArr[i]
			hi++
			i++
		}
		for j &lt; aLen {
			tmpHelp[hi] = tmpArr[j]
			hi++
			j++
		}

		hSave = !hSave
	}

	if !hSave {
		copy(arr, helpArr)
	}
}

答案8

得分: 0

这是我根据《算法图解》一书中的Python示例编写的解决方案。感觉它没有那么令人费解,所以我想分享一下。

package main

import "fmt"

func main() {
    list := []int{33, 10, 15, 45, 65, 16, 5}

    fmt.Println(quicksort(list))
}

func quicksort(list []int) []int {
    if len(list) < 2 {
        return list
    }
    if len(list) == 2 {
        left, right := 0, len(list)-1
        if list[0] > list[1] {
            list[left], list[right] = list[right], list[left]
        }
        return list
    }

    pivot := list[0]
    var less []int
    var greater []int

    for i := range list {
        if list[i] < pivot {
            less = append(less, list[i])
        }
        if list[i] > pivot {
            greater = append(greater, list[i])
        }
    }

    return append(append(quicksort(less), pivot), quicksort(greater)...)
}

你可以在Go Playground上运行它这里

英文:

Here's a solution I built following the Python examples in the book Grokking algorithms. It felt a little less mind bending, so I thought I'd share.

package main

import &quot;fmt&quot;

func main() {
	list := []int{33, 10, 15, 45, 65, 16, 5}

	fmt.Println(quicksort(list))
}

func quicksort(list []int) []int {
	if len(list) &lt; 2 {
		return list
	}
	if len(list) == 2 {
		left, right := 0, len(list)-1
		if list[0] &gt; list[1] {
			list[left], list[right] = list[right], list[left]
		}
		return list
	}

	pivot := list[0]
	var less []int
	var greater []int

	for i := range list {
		if list[i] &lt; pivot {
			less = append(less, list[i])
		}
		if list[i] &gt; pivot {
			greater = append(greater, list[i])
		}
	}

	return append(append(quicksort(less), pivot), quicksort(greater)...)
}

You can run it on the Go Playground here.

huangapple
  • 本文由 发表于 2013年4月4日 13:04:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/15802890.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定