快速排序(Quicksort)

同冒泡排序一样,快速排序也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。

不同的是,冒泡排序在每一轮只把一个元素冒泡到数列的一端,而快速排序在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列一边,比它小的元素移动到数列的另一边,从而把数列拆解成了两个部分。

这种方法叫分治法。

快排的主要思想就是分治,每次把大于基准和小于基准的数分在两边,每部分在下次又分为两个部分,直到不可再分。

这种方法平均情况下需要进行 logn 轮,因此快速排序算法的平均时间复杂度是 O(nlogn)。但是有时选基准会选到区间内的最大或最小值,这样每次就只是确定了基准的位置,没有发挥分治法的优势,这种极端条件下,会进行 n 轮,所以快排最坏的时间复杂度为 O(n²)

确定好基数后就是把数分为大于基数和小于基数的两部分了,分数有两种方法:

1. 挖坑法

2. 指针交换法

 

挖坑法

挖坑法顾名思义就是挖坑,挖完坑找数填坑。

我们举个栗子来理解他,规定一个数组:

序号01234567
数字147165322810
指针i      j

首先确定一个基准元素 key = a[0] = 14,此时 a[0] 这个位置就相当于一个坑。初始时左右各一个指针,i = 0,j = 7,i 将要指向大于基准的数的位置,j 将要指向小于基准的位置,因为要保证基准数左边全部是小于基准的数,基准右边全部大于基准,所以 j 从最右边开始左移,直到指向小于基准的数,放到左边的坑里,此时就出现了一个新坑(j 指向的小于基准的数的位置),i 再从左开始找到大于基准的数,填到右边的坑里,这时 i 指向的大于基准的数的位置就是一个新坑,重复上述步骤,直到没有可以改变的位置。

下面来模拟过程(红色位置为坑,蓝色位置为找到的要填的数的位置)

序号

0

123456

7

数字147165322810
指针i      j

从右开始找到位置 7 为小于基准的数的位置,把 a[7] 这个数填到 0 这个坑里,结果:

序号

0

123456

7

数字

10

7165322810
指针i      j

此时 7 就是一个新坑,再从左开始找比基准数大的值

序号01

2

3456

7

数字107165322810
指针  i    j

找到了 2 这个位置,再把 a[2] 填到 7 这个坑里,结果:

序号01

2

3456

7

数字1071653228

16

指针  i    j

2 为新坑,再从右开始找比基准小的数填到左边

序号01

2

345

6

7
数字107

8 

5322816
指针  i   j 

将 a[6] 填入 2 中,6 为坑,继续从左边找

序号01234

5

6

7
数字107 8 5322

22

16
指针     ij 

找到 5 的位置,将 a[5] 填到坑6 里,5 为新坑,j 继续向左移动

序号01234

5

67
数字107 8 53222216
指针     i , j  

此时 i, j 重合,5 位置为坑,把之前的基准 key 填入坑 5,这样一次循环就结束了,结果:

序号01234

5

67
数字107 8 53

14

2216
指针     i , j  

主要代码

void quick_sort(int a[], int l, int r)
{
	if(l<r)
	{
		int i = l, j = r, key = a[l];
		while(i<j)
		{
			while(i<j && a[j]>=key) j--;
			
			if(i<j) a[i++] = a[j];
			
			while(i<j && a[i]<=key) i++;
			
			if(i<j) a[j--] = a[i];
		}
		a[i] = key;
		quick_sort(a,l,i-1);
		quick_sort(a,i+1,r);
	}
}

运行过程

 

指针交换法

挖坑法是找到一个放一个,而指针交换法是同时找到两个,交换他们的位置

同样的,来举栗子:

序号01234567
数字147165322810
指针i      j

没错还是这个数组,还是这个基准 key = a[0] = 14,还是这个指针 i = 0,i = 7,不同的是,这次小 i 和小 j 一同奔跑(为了能快点见面也是不容易),i 还是向右找到大于基准的数,j 还是向左找到小于基准的数

序号01

2

3456

7

数字147165322810
指针  i    j

找到后交换两个指针指向的数字

序号01

2

3456

7

数字147

10

53228

16

指针  i    j

交换后继续奔跑

序号01234

5

6

7
数字147105322816
指针     ij 

继续交换

序号01234

5

6

7
数字1471053

8

22

16
指针     ij 

交换后会发现,挨着了,怎么办,这时要先让小 j 跑,跑到小 i 的位置,为什么呢,先来看一下小 j 先跑的结果

序号01234

5

67
数字147105382216
指针     i, j  

别忘了还有个基准等着分配,此时就要和基准交换了

序号

0

1234

5

67
数字

8

71053

14

2216
指针     i, j  

有木有感觉很完美,这就是小 j 先跑的结果,咱们再返回去看小 i 先跑的结果

序号012345

6

7
数字147105382216
指针      i, j 

然后进行交换

序号

0

12345

6

7
数字

22

710538

14

16
指针      i, j 

还记不记得快排的思想是什么,基准左边的数都要小于基准,现在 22 在 14 左边就出错了,所以每次移动,要先让小 j 动

主要代码

void quick_sort(int a[], int l, int r)
{
	if(l > r) return;
	
	int i = l, j = r, key = a[l];
	while(i!=j)
	{
		while(i<j && a[j]>=key) j--;
		
		while(i<j && a[i]<=key) i++;
		
		if(i<j)
		{
			int t = a[i];
			a[i] = a[j];
			a[j] = t;
		}
	}
	a[l] = a[i];
	a[i] = key;
	
	quick_sort(a,l,i-1);
	quick_sort(a,i+1,r);
}

运行过程

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值