排序二

前言

冒泡,插入,选择时间复杂度O(n^2),三者中插入比较常用。

归并,快速排序时间复杂度O(nlogn),比插入更加受欢迎。

五种排序最受欢迎的是快速排序,常用程度为:快速排序>归并排序>插入排序>冒泡排序>选择排序

实现

归并排序

package main

import "fmt"

func MergeSort(arr []int) {
	n := len(arr)
	if n <= 1 {
		return
	}

	MergeSortCore(arr, 0, n-1)

}

func MergeSortCore(arr []int, l, r int)  {
	if l >= r {
		return
	}

	mid := (l+r) / 2

	MergeSortCore(arr, l, mid)
	MergeSortCore(arr, mid+1, r)

	MergeCore(arr, l, mid, r)
}

func MergeCore(arr []int, left, mid, right int) {
	tmp := make([]int, right-left+1)

	l, r := left, mid+1
	k := 0

	for l <= mid && r <= right {
		if arr[l] < arr[r] {
			tmp[k] = arr[l]
			l++
		} else {
			tmp[k] = arr[r]
			r++
		}
		k++
	}

	for ;l<=mid;l++ {
		tmp[k] = arr[l]
		k++
	}

	for ;r<=right;r++ {
		tmp[k] = arr[r]
		k++
	}

	copy(arr[left:right+1], tmp)
}

func main() {

	arr := []int{2,4,8,1,3,6,7,5,0}
	fmt.Printf("arr : %v\n", arr)
	MergeSort(arr)
	fmt.Printf("res : %v\n", arr)
	
}

复杂度分析

  1. 时间复杂度O(nlogn)。
  2. 空间复杂度O(n)。
  3. 稳定排序算法。
  4. 不是原地排序,相比快速排序此是致命的缺点,所以不如快速排序应用广泛。

快速排序

// 版本 1

package main

import "fmt"

func InsertSort2(m map[int]int) {

	l := len(m)
	if l <= 1 {
		return
	}

	for i := 1; i < l; i++ {
		tmp := m[i]
		j := i-1
		for ;j >= 0; j-- {
			if tmp < m[j] {
				m[j+1] = m[j]
			} else {
				break
			}
		}
		m[j+1] = tmp
	}
}


// 简单版,但是空间复杂度比较高
func QuickSort(m []int) []int {
	l := len(m)
	if l == 0 {
		return m
	}

	item := m[l-1]
	left, right := []int{}, []int{}

	for i := 0; i < l-1; i++ {
		if m[i] < item {
			left = append(left, m[i])
		} else {
			right = append(right, m[i])
		}
	}

	return append(append(QuickSort(left), item), QuickSort(right)...)
}



func QuickSort(m map[int]int, p, r int)  {

	if r <= p {
		return
	}

	// r 为基准元素
	i, j := p, p

	for q := p; q <= r-1; q++ {
		if m[q] <= m[r] {
			i++
			j++
			continue
		} else {
				j++
		}
		if m[j] < m[r] {
			m[i], m[j] = m[j], m[i]
			i++
		}
	}
	m[i], m[r] = m[r], m[i]

	QuickSort(m, p, i-1)
	QuickSort(m, i+1, r)
}


func main() {
	m := make(map[int]int)
	m[0] = 9
	m[1] = 2
	m[2] = 8
	m[3] = 3
	m[4] = 7
	m[5] = 4
	m[6] = 1

	//InsertSort2(m)
	QuickSort(m, 0, 6)

	for i := 0; i < len(m); i++ {
		fmt.Printf("%v : %v \n", i, m[i])
	}

	
}


// 版本 2




package main

import (
	"fmt"
)

func QuickSort(arr []int) {
	n := len(arr)
	if n <= 1 {
		return
	}

	QuickSortCore(arr, 0, n-1)

}

func QuickSortCore(arr []int, start, end int) {
	if start >= end {
		return
	}


	pivot := partition(arr, start, end)


	QuickSortCore(arr,start, pivot-1)
	QuickSortCore(arr, pivot+1, end)
}

func partition(arr []int, start, end int) int {
	tmp := arr[end]

	i, j := start, start // i 维护数组前半段比tmp小的指针,j 遍历整个数组用于跟tmp比较指针

	for ;j<=(end-1); j++ {
		if arr[j] < tmp {
			arr[i], arr[j] = arr[j], arr[i]
			i++
		}
	}

	arr[i], arr[end] = arr[end], arr[i]

	return i
}

func main() {
	arr := []int{2,4,8,1,3,6,7,5,0, 10}
	fmt.Printf("arr : %v\n", arr)
	QuickSort(arr)
	fmt.Printf("res : %v\n", arr)
}

复杂度分析

  1. 时间复杂度O(nlogn)。
  2. 原地排序。
  3. 非稳定排序算法:因为要比较交换位置,如:6,7,10,6,10,4,8,9,5 第一轮比较过后,首位的6被交换到10后面,故不稳定。

比较

归并排序与快排都运用了分治思想,递归实现,思路很相似,而二者处理思路正好想返。

  1. 归并排序,先分区,在合并同时排序到临时数组,最终copy到原数组(非原地排序)。
  2. 快速排序,寻找基准点后,先排序(粗略小于基准点在左侧,大于基准点在右侧),然后在分别对应的基准点两个区,在进行排序,分区,最终递归返回。

注意点

  1. 归并排序,习惯性取中间位置为分区点,分别左右分区,切分区点是归属于左分区,参与最后的递归中“归”时候得比较。
  2. 快速排序,习惯性取最后一个位置作为比较的基准点,然后通过巧妙的比较交换位置的方式,最终将小与基准点都放在左侧,大于基准点的放在右侧。然后分别对应左侧小与分区,和右侧大于分区分别递归(基准点位置为最终排序好的所在数组位置,不在参与任何运算)运算即可。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值