基础算法----快速排序(Quick Sort)

算法稳定性,时间复杂度,空间复杂度等算法基础知识经典算法----基础知识
冒泡排序基础算法----冒泡排序
选择排序基础算法----选择排序
插入排序基础算法----插入排序
快速排序基础算法----快速排序
希尔排序基础算法----希尔排序
归并排序基础算法----归并排序
计数排序基础算法----计数排序
桶排序基础算法----桶排序
基数排序基础算法----基数排序
堆排序基础算法----堆排序

一 算法简介

选取一个基准元素(通常是序列的第一个或最后一个元素)作为基准值,小于基准值的元素移到基准值的左边,大于或等于基准值的元素移到基准值的右边
然后分别对左、右子序列递归排序,当左、右子序列排序完成(长度小于等于1),整个序列的排序完成

快速排序是一种基于分治思想的比较排序算法

二 时间复杂度,空间复杂度

一) 时间复杂度

每一趟都需要比较n-1次,每一趟结束后把原序列分成两部分:X1,X2
时间复杂度 T(n) = D(n) + T(X1) + T(X2)
其中D(n) = n-1,表示每一趟比较的次数


最好情况
每次取的基准值将当前序列分为长度接近的两个子序列,经过log 2n趟划分,即可得到长度为1的子序列,时间复杂度为O(nlog 2n)

T(n) = D(n) + 2T(n/2)
   = D(n) + 2D(n/2) + 4T(n/4)
   = D(n) + 2D(n/2) + 4D(n/4) + 8T(n/8)
   = D(n) + 2D(n/2) + … + 2kD(n/2k)

其中:
D(n) = n -1,表示每一趟比较的次数
k = log2n,经过log2n趟划分,即可得到长度为1的子序列

所以:
T(n) = n-1 + n-2 + … + n - 2k
   = nlog2n - 2n +1


最坏情况
每次取的基准值是最大或最小,一个子序列长度为0,一个子序列长度为原序列长度-1, 时间复杂度为O(n 2)
T(n) = D(n) + T(n-1)
   = D(n) + D(n-1) + T(n-2)
   = D(n) + D(n-1) + D(n-2)+ T(n-3)
   = D(n) + D(n-1) +D(n-2)+ D(n-3) + … + D(2) + D(1)
   = n(n-1)/2


平均时间复杂度为 T(n) = O(nlog2n)

二) 空间复杂度

快速排序使用原地排序,存储空间复杂度为:S(1)
由于需要递归,程序栈层数范围在 S(n) = O(log2n)~O(n)

快速排序的空间复杂度为:S(n) = O(log2n)~O(n)

三 算法稳定性

与基准值相同的元素,因为分区而导致顺序不一致,如:{4, 2, 4, 5, 1, 6, 7},选择第三个元素作为基准值时,第一个元素4因为分区移动到基准值的右边
快速排序是一种不稳定的排序算法

四 基于golang代码的实现

一) 基础递归版

1 步骤

1) 选取第一个元素作为基准值(arr[0])

2) 从第二个元素(i = 1)开始从前往后遍历序列,循环比较,小于基准值的移到左边,大于等于基准值的移到右边。其中:小于基准值的元素移到左边的顺序是索引从小到大,大于等于基准值的元素移到右边的顺序是索引从大到小

3) 左、右子序列递归排序

2 过程图

使用 {40, 30, 50, 40, 20, 60, 10, 70} 演示从小到大的排序过程

其中
基础递归版----图示说明

1) 原始序列拆分左、右子序列
基础递归版----原始序列拆分左、右子序列

2) 递归左子序列
基础递归版----递归左子序列

2.1) 左子序列的左子序列

基础递归版----左子序列的左子序列

2) 递归右子序列
基础递归版----递归右子序列
2.1) 右子序列的左子序列
基础递归版----右子序列的左子序列

3 代码实现

func QuickSort(arr []int) {

	if len(arr) <= 1 {
		return
	}

	head, tail := 0, len(arr)-1
	for i := 1; head < tail; {
		if arr[i] < arr[head] {
			arr[i], arr[head] = arr[head], arr[i]
			head++
			i++
		} else {
			arr[i], arr[tail] = arr[tail], arr[i]
			tail--
		}
	}

	QuickSort(arr[:head])
	QuickSort(arr[head+1:])

	return
}

其中

head:存放小于基准值的位置(索引,初值为0)

tail:存放大于或等于基准值的位置(索引,初值为序列长度-1)

如果当前元素(arr[i])小于基准值,交换位置到基准值的左边,每交换一次,head+1,即[0: head]之间的元素已经小于基准值

如果当前元素(arr[i])大于或等于基准值,交换位置到基准值的右边,每交换一次,tail-1,即[tail: 序列长度-1]之间的元素已经大于或等于基准值

当前元素(arr[i])大于或等于基准值,不用 i++,因为交换后的arr[i](arr[i]=arr[tail])不一定大于等于基准值,需要再次进行比较

二) hoare法

1 步骤

1) 选取第一个元素作为基准值(arr[0])

2) 定义两个变量: head, tail,tail从右向左移动,找到比基准值小的元素停下;head从左向右移动,找到比基准值大的元素停下;然后交换head, tail的位置

3) 重复步骤2),直到head和tail相遇

4) 交换基准值和相遇的位置

5) 左、右子序列递归排序

2 过程图

使用 {40, 30, 50, 40, 20, 60, 10, 70} 演示从小到大的排序过程


1) 原始序列拆分左、右子序列
hoare法----原始序列拆分左、右子序列

2) 递归左子序列
hoare法----递归左子序列


2.1) 左子序列的右子序列
hoare法----左子序列的右子序列

3) 递归右子序列
hoare法----递归右子序列

3 代码实现

func QuickSort(arr []int) {

	if len(arr) <= 1 {
		return
	}

	baseIndex, head, tail := 0, 0, len(arr)-1
	for head < tail {
		//从右往左找第一个小于基准值的元素
		for head < tail && arr[tail] >= arr[baseIndex] {
			tail--
		}
		//从左往右找第一个大于基准值的元素
		for head < tail && arr[head] <= arr[baseIndex] {
			head++
		}
		//大于基准值的元素和小于基准值的元素交换位置
		arr[head], arr[tail] = arr[tail], arr[head]
	}
	//交换baseIndex和head, tail相遇位置的元素
	arr[baseIndex], arr[head] = arr[head], arr[baseIndex]
	
	QuickSort(arr[:head])
	QuickSort(arr[head+1:])

	return
}

三) 挖坑法

挖坑法思路与hoare法思路大致相同

1 步骤

1) 选取第一个元素作为基准值(arr[0]),保存到变量中,该位置类似被挖了一个坑

2) 定义两个变量: head, tail,tail从右向左移动,找到比基准值小的元素停下,把找到的元素填入坑中,找到的元素的位置又形成一个新的坑

3) head从左向右移动,找到比基准值大的元素停下,把找到的元素填入坑中,找到的元素的位置又形成一个新的坑

4) 重复步骤2)、3),直到head和tail相遇

5) 基准值填入相遇的位置

6) 左、右子序列递归排序

2 过程图

使用 {40, 30, 50, 40, 20, 60, 10, 70} 演示从小到大的排序过程

1) 原始序列拆分左、右子序列
挖坑法----原始序列拆分左、右子序列


2) 递归左子序列
挖坑法----递归左子序列

2.1) 左子序列的右子序列
挖坑法----左子序列的右子序列

3) 递归右子序列

挖坑法----递归右子序列

3 代码实现

func QuickSort(arr []int) {

	if len(arr) <= 1 {
		return
	}

	//基准值
	baseValue := arr[head]
	baseIndex, head, tail := 0, 0, len(arr)-1
	for head < tail {
		//从右往左找第一个小于基准值的元素
		for head < tail && arr[tail] >= baseValue {
			tail--
		}
		//找到的元素填入坑中
		arr[baseIndex] = arr[tail]
		//更新坑, 新坑就是找到的元素的位置
		baseIndex = tail

		//从左往右找第一个大于基准值的元素
		for head < tail && arr[head] <= baseValue {
			head++
		}
		//找到的元素填入坑中
		arr[baseIndex] = arr[head]
		//更新坑, 新坑就是找到的元素的位置
		baseIndex = head
	}

	//基准值填入head, tail相遇的位置
	arr[baseIndex] = baseValue

	QuickSort(arr[:head])
	QuickSort(arr[head+1:])
}
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值