常见排序算法

310 阅读3分钟

排序算法分析

冒泡排序

定义

1、两个元素对比,然后将大得向右移动,每次循环都将最大得元素移动到最右侧
2、如果元素个数小于2个,则表示原有数据已经有序

使用两层循环实现

// bubblingSort
// 冒泡排序
func bubblingSort(data []int) {
	length := len(data)
	if length < 2 {
		return
	}

	// 外层循环,告知排序得次数
	for i := 0; i < length-1; i++ {
		// 内层循环,用户循环比较,每一步骤都将最大得移动到最右侧
		for j := 0; j < length-i-1; j++ {
			log.Println(j)
			// 是否会溢出?
			if data[j] > data[j+1] {
				data[j], data[j+1] = data[j+1], data[j]
			}
		}
	}
}

选择排序

定义

1、找到数组中最大的元素,与数组最后一位元素交换
2、如果元素个数小于2个,则表示原有数据已经有序

使用两层循环实现

// selectSort
// 选择排序
func selectSort(data []int) {
	length := len(data)
	if length < 2 {
		return
	}
	for i := 0; i < length-1; i++ {
		// 假定最大值索引位置为第一个
		maxpos := 0

		// 内部循环找到真实得最大值索引位置
		for j := 0; j < length-i; j++ {
			if data[j] > data[maxpos] {
				maxpos = j
			}
		}
		data[length-i-1], data[maxpos] = data[maxpos], data[length-i-1]
	}
}

插入排序

定义

1、将一个元素插入到已有序的数组中,在初始时未知是否存在有序的数据,因此将元素第一个元素看成是有序的;
2、与有序的数组进行比较,比它大则直接放入,比它小则移动数组元素的位置,找到个合适的位置插入;
3、当只有一个数时,则不需要插入了,因此需要n-1趟排序,比如10个数,需要9趟排序

代码

// insertSort
// 插入排序
func insertSort(data []int) {
	length := len(data)
	if length < 2 {
		return
	}

	// 假设第一个元素为有序数组
	for i := 1; i < length; i++ {
		// 选取一个值 向有序集合中插入
		temp := data[i]

		// 将选取的值插入到有序集合的指定位置
		j := i - 1
                // 挑选的值与倒序与有序集合对比,如果选择的值小于则移动有序集合索引
		for ; j >= 0 && data[j] > temp; j-- {
			data[j+1] = data[j]
		}
		data[j+1] = temp
	}
}

希尔排序

定义

希尔排序实质上就是插入排序的增强版,希尔排序将数组分隔成n组来进行插入排序,**直至该数组宏观上有序,**最后再进行插入排序时就不用移动那么多次位置了~

代码

// shellSort
// 希尔排序
// 希尔增量一般是gap = gap / 2,只是比普通版插入排序多了这么一个for循环罢了
func shellSort(data []int) {
	length := len(data)
	if length < 2 {
		return
	}
	var temp int
	var j int
	// 将一个数组分成多个片段
	for d := length / 2; d > 0; d = d / 2 {
		// 对每一组片段进行插入排序
		for x := 0; x < d; x++ {
			// 下面为一个插入排序
			for i := x + d; i < length; i = i + d {
				temp = data[i]
				j = i - d
				for ; j >= 0 && data[j] > temp; j = j - d {
					data[j+d] = data[j]
				}
				data[j+d] = temp
			}
		}
	}
}

归并排序

定义

将两个已排好序的数组合并成一个有序的数组
将元素分隔开来,看成是有序的数组,进行比较合并
断拆分和合并,直到只有一个元素

代码


/**
 * @desc 归并排序
 * 思路: 选择中间索引将数组分割为两个,然后组合两个数组按大小顺序组合
 * 时间复杂度 O(nlog2n)
 * 空间复杂度 O(n) + O(log2n)
 * 稳定性:稳定
 */
func MergeSort(item []int) []int {
	mergeSort(item, 0, len(item)-1)
	return item
}

// @param item 排序数组
// @param 开始索引位置
// @param 结束索引位置
func mergeSort(item []int, left, right int) {
	if left < right {
		center := (left + right) / 2
		mergeSort(item, left, center)
		mergeSort(item, center+1, right)
		merge(item, left, center+1, right)
	}
}

// @desc 合并两个数组
func merge(item []int, left, center, right int) {
	// 左侧数组大小
	leftData := make([]int, center-left)
	// 右侧数组大小
	rightData := make([]int, right-center+1)

	// 向两个数组中填充数据
	for i := left; i < center; i++ {
		leftData[i-left] = item[i]
	}

	for i := center; i <= right; i++ {
		rightData[i-center] = item[i]
	}

	// 用于遍历两个数组
	i, j := 0, 0
	// 数组中的第一个元素
	index := left
	// 循环对比合并两个数组
	for i < len(leftData) && j < len(rightData) {
		if leftData[i] < rightData[j] {
			item[index] = leftData[i]
			i++
		} else {
			item[index] = rightData[j]
			j++
		}
		// 增加后索引增加1
		index++
	}

	// 将数据中剩余的元素继续插入
	for i < len(leftData) {
		item[index] = leftData[i]
		i++
		index++
	}
	for j < len(rightData) {
		item[index] = rightData[j]
		j++
		index++
	}
}

func mergeSortV2(item []int, left, center, right int) {
	temp := make([]int, right-left+1)
	i := 0
	// 左边开始
	start := left
	// 右边开始
	end := center + 1
	for start <= center && end <= right {
		if item[start] < item[end] {
			temp[i] = item[start]
			start++
		} else {
			temp[i] = item[end]
			end++
		}
		i++
	}

	for start <= center {
		temp[i] = item[start]
		i++
		start++
	}
	for end <= end {
		temp[i] = item[end]
		i++
		end++
	}
	// 将结果返回给原素组
	for i := 0; i < len(temp); i++ {
		item[left+i] = temp[i]
	}
}

快速排序

定义

1、在数组中找一个元素(节点),比它小的放在节点的左边,比它大的放在节点右边。一趟下来,比节点小的在左边,比节点大的在右边;
2、不断执行这个操作....

使用递归实现

优点:逻辑简单,一看就懂
缺点:浪费空间,因为每次递归都需要申请两个切片保存左右节点

// quickSort
// 快速排序,使用递归方式
func quickSort(data []int) []int {
	if len(data) == 0 {
		return data
	}
        // 取第一个元素作为比较节点
	// temp := data[0]
	left := []int{}
	right := []int{}

	// 此处循环从1开始,第一个节点已经拿出来用于比较了
	for i := 1; i < len(data); i++ {
		if data[i] > data[0] {
			right = append(right, data[i])
		} else {
			left = append(left, data[i])
		}
	}
	left = quickSort(left)
	right = quickSort(right)
	return append(append(left, data[0]), right...)
}

递归优化版本

使用两个节点交换,减少内存的申请

// quickSort
// 快速排序,使用递归方式
func quickSort(data []int, left, right int) {
	if left >= right {
		return
	}

	start := left
	end := right

	// 选择第一个节点
	value := data[left]
	// 根据选取的节点将数组 分成两部分
	for left < right {
		// 如果右侧节点大于选取节点则向左移动指针
		for right > left && data[right] >= value {
			right--
		}
		data[left] = data[right]

		// 如果左边节点小于选取节点则向右移动指针
		for left < right && data[left] <= value {
			left++
		}
		data[right] = data[left]
	}
	// 将选取得值赋值左指针
	data[left] = value
	quickSort(data, start, left-1)
	quickSort(data, left+1, end)
}

基数排序

定义

基数排序(桶排序):将数字切割成个、十、百、千位放入到不同的桶子里,放一次就按桶子顺序回收一次,直至最大位数的数字放完~那么该数组就有序了。

代码


// radixSort
// 基数排序
// 时间复杂度:O(wn) w:元素的最大长度,N元素的个数
// 思路:将所有待比较数值统一为同样的数位长度,数位较短的数前面补充零;
// 然后从最低位开始,依次进行一次排序
func radixSort(item []int) {
	if len(item) == 0 {
		return
	}
	// 首先得到数组中最大的数的位数
	// 假设第一个元素为最大
	max := item[0]

	for i := 1; i < len(item); i++ {
		if item[i] > max {
			max = item[i]
		}
	}
	// 得到最大数的几位数
	maxlength := len(strconv.Itoa(max))

	// 定义一个二维数组,表示10个桶,每个桶就是一个一位数组
	// 1、二维数组包含10个一维数组
	// 2、为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length
	// 3、明确,基数排序是使用空间换时间的经典算法
	//count := len(item)
	bucket := make(map[int][]int, 0)

	// 记录每个桶实际存放多少数据
	bucketElementCounts := make([]int, 10)

	// 根据最大位数进行循环
	for i, n := 0, 1; i < maxlength; i++ {
		// 针对每个元素对应位进行排序处理(第一位、第二位、第三位)
		for j := 0; j < len(item); j++ {
			// 取出每个元素的对应位的值
			digitOfElement := item[j] / n % 10
			// 放入到对应桶中
			bucket[digitOfElement] = append(bucket[digitOfElement], item[j])

			// 更新对应桶的元素数量
			bucketElementCounts[digitOfElement]++
		}

		// 按照桶的顺序(一维数组的下标一次取出数据,放入原来数组)【数组按顺序输出】
		index := 0
		for key, number := range bucketElementCounts {
			fmt.Println(key, number)
			// 将每个桶的元素循环输出
			for k := 0; k < number; k++ {
				item[index] = bucket[key][k]
				index++
			}
			delete(bucket, key)
			bucketElementCounts[key] = 0
		}
		// 用于处理位数 x/n%10 取出每一个元素的指定位
		n = n * 10
	}
}

计数排序

定义

代码


func countSort(item []int) []int {
	// 获取数据最大的值和最小的值用于范围
	max := item[0]
	min := item[0]
	for _, value := range item {
		if value > max {
			max = value
		}
		if value < min {
			min = value
		}
	}
	// 需要桶的数量
	lenth := max - min

	// 根据数列最大值确定统计数组的长度,声明一个桶用于存放数据
	bucket := make([]int, lenth+1)

	// 遍历数列,统计每个索引的个数,填充到桶中
	for i := 0; i < len(item); i++ {
		index := item[i] - min
		bucket[index]++
	}

	// 遍历桶,输出结果
	//index := 0
	rs := make([]int, 0)
	for i := 0; i < len(bucket); i++ {
		// 如果存在数据
		if bucket[i] > 0 {
			for j := 0; j < bucket[j]; j++ {
				rs = append(rs, i+min)
			}
		}
	}
	return rs
}

总结

以上主要是使用golang实现了常见的排序算法

参考

八大基础排序总结
漫画:什么是计数排序
归并排序就这么简单