go语言实现--常用的排序算法

以前用过c和php实现过一些常用的排序算法,现在用golang把它们再实现一遍

1.冒泡排序

冒泡排序的基本思想是,对相邻的元素进行两两比较,顺序相反则进行交换,这样,每一趟会将最小或最大的元素“浮”到顶端,最终达到完全有序。时间复杂度为O(n2)

代码如下:

package main

import "fmt"

func main() {
	arr := []int{8, 4, 2, 9, 10, -3, 3, 20, 15, -1}
	BubbleSort(arr)
	fmt.Println(arr)
}

func BubbleSort(arr []int) {
	length := len(arr)
	for i := 0; i < length-1; i++ {
		flag := true //若为true,则表示此次循环没有进行交换,也就是待排序列已经有序
		for j := 0; j < length-1-i; j++ {
			if arr[j] > arr[j+1] {
				arr[j], arr[j+1] = arr[j+1], arr[j]
				flag = false
			}
		}
		if flag {
			break
		}
	}
}

2.简单选择排序

基本思想为每一趟从待排序的数据元素中选择最小(或最大)的一个元素作为首元素,直到所有元素排完为止,简单选择排序是不稳定排序。时间复杂度为O(n2),比上面的冒泡好一点。

  在算法实现时,每一趟确定最小元素的时候会通过不断地比较交换来使得首位置为当前最小,交换是个比较耗时的操作。其实我们很容易发现,在还未完全确定当前最小元素之前,这些交换都是无意义的。我们可以通过设置一个变量min,每一次比较仅存储较小元素的数组下标,当轮循环结束之后,那这个变量存储的就是当前最小元素的下标,此时再执行交换操作即可。

代码如下:

package main

import "fmt"

func main() {
	arr := []int{8, 4, 2, 9, 10, -3, 3, 20, 15, -1}

	SelectSort(arr)
	fmt.Println(arr)
}

func SelectSort(arr []int) {
	length := len(arr)
	for i := 0; i < length-1; i++ {
		min := i //每一趟的开始把首元素的下标作为最小元素的下标
		for j := i + 1; j < length; j++ {
			if arr[j] < arr[min] {
				min = j
			}
		}
		if min != i {
			arr[i], arr[min] = arr[min], arr[i]
		}
	}
}


3.直接插入排序

 

直接插入排序基本思想是每一步将一个待排序的记录,插入到前面已经排好序的有序序列中去,直到插完所有元素为止。

简单插入排序在最好情况下,需要比较n-1次,无需交换元素,时间复杂度为O(n);在最坏情况下,时间复杂度依然为O(n2)。但是在数组元素随机排列的情况下,插入排序还是要优于上面两种排序的。

代码如下:

package main

import "fmt"

func main() {
	arr := []int{8, 4, 2, 9, 10, -3, 3, 20, 15, -1}
	InsertSort(arr)
	fmt.Println(arr)
}

func InsertSort(arr []int) {
	length := len(arr)
	for i := 1; i < length; i++ { //把第一个元素作为有序序列
		j := i
		if arr[i] < arr[i-1] {
			for j > 0 && arr[j] < arr[j-1] {
				arr[j], arr[j-1] = arr[j-1], arr[j]
				j--
			}
		}
	}
}

4.希尔排序

 

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。平均的时间复杂度O(nlogn)。但是它相对比较简单,它适合于数据量在5000以下并且速度并不是特别重要的场合。它对于数据量较小的数列重复排序是非常好的。最坏的情况下时间复杂度是 O(n^2)

代码如下:

package main

import "fmt"

func main() {
	arr := []int{8, 4, 2, 9, 10, -3, 3, 20, 15, -1}
	ShellSort(arr)
	fmt.Println(arr)
}

func ShellSort(arr []int) {
	length := len(arr)
	//增量gap,并逐步缩小增量
	for gap := length / 2; gap > 0; gap /= 2 {
		//从第gap个元素,逐个对其所在组进行直接插入排序操作
		for i := gap; i < length; i++ {
			j := i
			for j-gap >= 0 && arr[j] < arr[j-gap] {
				arr[j], arr[j-gap] = arr[j-gap], arr[j]
				j -= gap
			}
		}
	}
}

5.堆排序

 

利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

    其基本思想为(大顶堆):

    1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区;

    2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];

    3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

    操作过程如下:

     1)初始化堆:将R[1..n]构造为堆;

     2)将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。

因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。

堆排序适合于数据量非常大的场合(百万数据),时间复杂度是 O(nlogn)

代码如下:

package main

import "fmt"

func main() {
	arr := []int{8, 4, 2, 9, 10, -3, 3, 20, 15, -1}
	HeapSort(arr)
	fmt.Println(arr)
}

func HeapSort(a []int) {
	length := len(a)
	if length == 0 {
		return
	}
	//构造初始堆
	//从下往上调整堆
	for i := length/2 - 1; i >= 0; i-- {
		adjustHeap(a, i, length-1)
	}
	//将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端
	for j := length - 1; j >= 0; j-- {
		a[0], a[j] = a[j], a[0]
		adjustHeap(a, 0, j-1)
	}
}

//调整堆
func adjustHeap(a []int, root, end int) {
	for k := 2*root + 1; k <= end; k = 2*k + 1 { //从i结点的左子结点开始,也就是2i+1处开始
		//选择出左右孩子较大的下标
		if k < end && a[k] < a[k+1] {
			k++
		}
		//如果子节点大于父节点,进行交换
		if a[k] > a[root] {
			a[root], a[k] = a[k], a[root]
			root = k
		} else {
			break
		}
	}
}


6.归并排序

 

归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。时间复杂度是 O(nlogn),空间复杂度O(n)。

代码如下:

package main

import "fmt"

func main() {
	arr := []int{8, 4, 2, 9, 10, -3, 3, 20, 15, -1}
	MergeSort(arr)
	fmt.Println(arr)
}

func MergeSort(arr []int) {
	length := len(arr)
	temp := make([]int, length) //提前开辟一块内存空间存放临时数据
	mSort(arr, 0, length-1, temp)
}

func mSort(arr []int, left, right int, temp []int) {
	if left < right {
		mid := (left + right) / 2
		mSort(arr, left, mid, temp)
		mSort(arr, mid+1, right, temp)
		//两边的子序列都是有序的,
		//如果左边的最大的元素比右边最小的元素大才需要合并
		if arr[mid] > arr[mid+1] {
			merge(arr, left, mid, right, temp)
		}
	}
}

func merge(arr []int, left, mid, right int, temp []int) {
	i := left
	j := mid + 1
	t := 0 //临时slice的指针
	for i <= mid && j <= right {
		if arr[i] <= arr[j] {
			temp[t] = arr[i]
			i++
		} else {
			temp[t] = arr[j]
			j++
		}
		t++
	}
	//将左序列剩余元素填充进temp中
	for i <= mid {
		temp[t] = arr[i]
		t++
		i++
	}
	//将右序列剩余元素填充进temp中
	for j <= right {
		temp[t] = arr[j]
		t++
		j++
	}
	t = 0
	//将temp中的元素全部拷贝到原数组中
	for left <= right {
		arr[left] = temp[t]
		left++
		t++
	}
}

7.快速排序

 

快速排序是一个就地排序,分而治之,大规模递归的算法。从本质上来说,它是归并排序的就地版本。快速排序可以由下面四步组成。

(1)如果不多于1个数据,直接返回。

(2)一般选择序列最左边的值作为支点数据。

(3)将序列分成2部分,一部分都大于支点数据,另外一部分都小于支点数据。

(4)对两边利用递归排序数列。

快速排序比大部分排序算法都要快。尽管我们可以在某些特殊的情况下写出比快速排序快的算法,但是就通常情况而言,没有比它更快的了。快速排序是递归的,对于内存非常有限的机器来说,它不是一个好的选择。时间复杂度是 O(nlogn),空间复杂度最差是O(n), 平均是O(logn)。

代码如下:

 

package main

import "fmt"

func main() {
	arr := []int{8, 4, 2, 9, 10, -3, 3, 20, 15, -1, 0, 8, 34, 44, -8}
	length := len(arr)
	QuickSort1(arr, 0, length-1)
	fmt.Println(arr)
}

func QuickSort1(arr []int, left, right int) {
	if left < right {
		i := arrAdjust(arr, left, right)
		QuickSort1(arr, left, i-1)  //调整左边的序列
		QuickSort1(arr, i+1, right) //调整右边的序列
	}
}

//返回调整后基准数的位置
func arrAdjust(arr []int, left, right int) int {
	x := arr[left]                            //基准
	i, j := left, right
	for i < j {
		//从右向左找小于x的数
		for i < j && arr[j] >= x {
			j--
		}
		//从左向右找大于x的数
		for i < j && arr[i] <= x {
			i++
		}
		//交换
		if i < j {
			arr[i], arr[j] = arr[j], arr[i]
		}
	}
	// i=j结束扫描
	// 基准数归位,现在左边的序列小于基准数,右边的序列大于基准数
	arr[left], arr[i] = arr[i], arr[left]
	return i
}

改进版的,基准数选用了序列的中间的一个数,当然亦可以比较第一个,中间的,最后的,这三个数选最大的把它交换到第一个数再作为基准。

代码如下:

package main

import "fmt"

func main() {
	arr := []int{8, 4, 2, 9, 10, -3, 3, 20, 15, -1, 0, 8, 34, 44, -8}
	length := len(arr)
	QuickSort1(arr, 0, length-1)
	fmt.Println(arr)
}

func QuickSort1(arr []int, left, right int) {
	if left < right {
		i := arrAdjust(arr, left, right)
		QuickSort1(arr, left, i-1)  //调整左边的序列
		QuickSort1(arr, i+1, right) //调整右边的序列
	}
}

//返回调整后基准数的位置
func arrAdjust(arr []int, left, right int) int {
	mid := (left + right) / 2
	arr[left], arr[mid] = arr[mid], arr[left] //可以选择中间的数作为基准
	x := arr[left]                            //基准
	i, j := left, right
	for i < j {
		//从右向左找小于x的数
		for i < j && arr[j] >= x {
			j--
		}
		//从左向右找大于x的数
		for i < j && arr[i] <= x {
			i++
		}
		//交换
		if i < j {
			arr[i], arr[j] = arr[j], arr[i]
		}
	}
	// i=j结束扫描
	// 基准数归位,现在左边的序列小于基准数,右边的序列大于基准数
	arr[left], arr[i] = arr[i], arr[left]
	return i
}


总结:

参考文章:

归并排序:http://www.cnblogs.com/chengxiao/p/6194356.html

堆排序:   http://www.cnblogs.com/chengxiao/p/6129630.html

快速排序:https://blog.csdn.net/morewindows/article/details/6684558

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值