常见比较排序

七大比较排序算法的golang实现

比较排序(英语:Comparison sort)是排序算法的一种,通过一个抽象的内容比较操作(通常是“小于或等于”操作)来确定两个元素中哪个应该放在序列前面。 –wiki

冒泡排序

func (b *BubbleSort) Sort(input []int) []int {
	l := len(input)
	for i := l - 1; i > 0; i-- {
		for j := 0; j < i; j++ {
			if input[j] > input[j+1] {
				input[j], input[j+1] = input[j+1], input[j]
			}
		}
	}
	return input
}

选择排序

func (i SelectSort) Sort(input []int) []int {
	l := len(input)
	for i := l - 1; i > 0; i-- {
		maxIndex := i
		for j := 0; j < i; j++ {
			if input[j] > input[maxIndex] {
				maxIndex = j
			}
		}
		input[maxIndex], input[i] = input[i], input[maxIndex]
	}
	return input
}

插入排序

func (i InsertSort) Sort(input []int) []int {
	l := len(input)
	for i := 1; i < l; i++ {
		j := i - 1
		// 取出即将插入的值
		key := input[i]
		// 从有序的最后一个开始,如果>key则将其赋值给后一个位置(值本身已经被缓存)
		for j >= 0 && input[j] > key {
			input[j+1] = input[j]
			j--
		}
		input[j+1] = key
	}
	return input
}

快速排序

使用二分的思想可以将时间复杂度降低到nlogn

type QuickSort struct{}

func (s QuickSort) Sort(nums []int) []int {
	if len(nums) < 2 {
		return nums
	}
	mid := s.partition(nums, len(nums)/2)
	s.Sort(nums[:mid])
	s.Sort(nums[mid+1:])
	return nums
}

func (s QuickSort) partition(nums []int, pivot int) int {
	n := len(nums)
	pivotValue := nums[pivot]
	nums[pivot], nums[n-1] = nums[n-1], nums[pivot]
	storeIndex := 0
	for i := 0; i < n; i++ {
		if nums[i] < pivotValue {
			nums[i], nums[storeIndex] = nums[storeIndex], nums[i]
			storeIndex++
		}
	}
	nums[n-1], nums[storeIndex] = nums[storeIndex], nums[n-1]
	return storeIndex
}

归并排序

归并的缺点是需要使用额外的空间,优点是每一路的归并可以并发完成,而且不需要将所有数据都加载到内存中

func (s MergeSort) subSort(input []int, reg []int) {
	l := len(input)
	if l <= 1 {
		return
	}
	mid := l / 2
	//对左右两部分进行归并排序
	s.subSort(input[:mid], reg[:mid])
	s.subSort(input[mid:], reg[mid:])
	
	i, j, k := 0, mid, 0
	// 如果有一路已经处理完,则退出循环
	for i < mid && j < l {
		//将较小的值放到临时数组中
		if input[i] < input[j] {
			reg[k] = input[i]
			k++
			i++
		} else {
			reg[k] = input[j]
			k++
			j++
		}
	}
	//将未归并完的一路直接连接到数组之后
	for i < mid {
		reg[k] = input[i]
		i++
		k++
	}
	for j < l {
		reg[k] = input[j]
		j++
		k++
	}
	//将临时数组的值拷贝
	copy(input, reg)
}

希尔排序

shellsort是对插入排序的优化,原理在于

  • 插入排序在排序几乎有序的数据时,效率高
  • 普通插入排序,每一个数据在一次操作中只能移动一步到最终位置
func (i ShellSort) Sort(input []int) []int {
	l := len(input)
	for gap := l / 2; gap > 0; gap /= 2 {
		for j := gap; j < l; j++ {
			tmp := input[j]
			k := j - gap
			for ; k >= 0 && input[k] > tmp; k -= gap {
				input[k+gap] = input[k]
			}
			input[k+gap] = tmp
		}
	}
	return input
}

堆排序

type HeapSort struct{}

// O(nlogn) 不稳定
func (s HeapSort) Sort(input []int) []int {
	//建堆(最大堆)
	s.createHeap(input)
	for i := len(input) - 1; i > 0; i-- {
		//将堆顶数据(最大值)移动到最终位置,并用堆最后一个元素填补
		input[0], input[i] = input[i], input[0]
		//调整堆
		s.shiftDown(input[:i], 0)
	}
	return input
}
// 建堆操作
func (s HeapSort) createHeap(input []int) {
	l := len(input)
	for i := l/2 - 1; i >= 0; i-- {
		s.shiftDown(input, i)
	}
}
// 使得堆重新有序
func (s HeapSort) shiftDown(heap []int, index int) {
	l := len(heap)
	// father
	f := index
	// child
	c := index*2 + 1
	for c < l {
		//如果右儿子存在,且大于左儿子
		if c+1 < l && heap[c+1] > heap[c] {
			c++
		}
		//如果孩子比父亲大,当前值下移
		if heap[f] < heap[c] {
			heap[f], heap[c] = heap[c], heap[f]
			f = c
			c = c*2 + 1
		} else {
			return
		}
	}
//	可以实现一个不交换的版本
//  可以理解为,为一个元素寻找一个正确的位置(空穴),算法模拟了空穴下移的过程
}

并发快排

type ConcurrentQSort struct{}

func (s ConcurrentQSort) Sort(nums []int) []int {
	n := len(nums)
	if n < 2 {
		return nums
	}
	mid := s.partition(nums, n/2)
    // 如果待排序数组比较小,则直接使用快排,防止使用过多的go routine
    // 在生成、调度、销毁上花费大量时间
	if n < 512 {
		s.Sort(nums[:mid])
		s.Sort(nums[mid+1:])
	} else {
        // 使用WaitGroup,等待子协程处理结束
		wg := sync.WaitGroup{}
		wg.Add(2)
		go func() {
			s.Sort(nums[:mid])
			wg.Done()
		}()
		go func() {
			s.Sort(nums[mid+1:])
			wg.Done()
		}()
		wg.Wait()
	}
	return nums
}

func (s ConcurrentQSort) partition(nums []int, pivot int) int {
	// 同快排
}

完整的测试代码在github