快速排序(附C/C++,Python代码)<学习笔记>

原理

我们对以下序列进行快速排序:

-123-252-31

下面我将第一个数作为基准数(pivot = arr[left],这边left指的是最左端数 )来进行比较(也可以是序列中其他数):

-123-252-31

我们需要以基准数为分界点,让基准数左边的数比它小(即左边序列成员都 <= -1),基准数右边的数比它大(即又边序列成员都 >= -1)。

排序过程

总序列

我们通过设置两个哨兵left,right (left < right),从序列两端开始与基准数(为了方便后面基准数我将标红)比较。

-123-252-31
leftright

我们先移动右边的哨兵(必须是右边先移动,后面我会解释为什么这样做):

由于 1 > -1,符合条件(条件:arr[right] >= pivot),所以我们将哨兵right左移到下一成员与基准数比较。

我们发现 -3 < -1 ,不符合条件,所以哨兵right停下。

-123-252-31
leftright

接下来我们将哨兵left右移,发现 2 > -1 , 不符合条件 (条件:arr[left] <= pivot),所以哨兵left停下。

-123-252-31
leftright

当两个哨兵停下时(即 arr[left] > pivot , arr[right] < pivot ),进行成员交换

将left和right处成员交换。

-1-33-25221
leftright

交换后就使得哨兵left处的值小于基准数,哨兵right处的值大于基准数。

然后我们移动右哨兵right继续比较,直到遇到不符合条件的成员,发现 -2 < -1 right哨兵停下

-1-33-25221
leftright

下面继续移动左哨兵left,发现 3 > -1 , left哨兵停下

-1-33-25221
leftright

将left和right处成员交换。

-1-3-235221
leftright

继续移动哨兵right,发现left哨兵与right哨兵相遇了,说明我们第一遍的排序结束了。

-1-3-235221
left,right

我们只需要将基准数的位置与哨兵相遇处交换:

int temp = pivot;
pivot = arr[left]; // 或者 pivot = arr[right];
arr[left] = temp;// 或者 arr[right] = temp;
-2-3-135221
左侧成员 <= -1left,right右侧成员 >= -1

经历了第一遍比较后,我们发现:

  • 基准数左侧的成员都小于基准数,右侧成员都大于基准数。
  • 但左边和右边的序列目前都还未排序完。

现在有了之前排序的思路,我们只要将其再分别处理左边和右边的序列即可。

左序列
-2-3-1 
leftright

由于 -3 < -2 ,哨兵right停下。我们移动哨兵left到下个位置,发现与哨兵right相遇。

同之前在总序列一样,只需要将基准数的位置与哨兵相遇处交换,可得:

-3-2-1 

这样我们第一遍排序后左测序列已经排序完了。

右序列
-135221
leftright

方法仍然是一样的,下面我不做详细解释了,直接演示下去。

arr[right] < pivot(基准数) , 哨兵right停下。

-135221
leftright

发现arr[left] > pivot,哨兵left停下。

-135221
leftright

将left和right处成员交换。

-131225
leftright

继续移动哨兵right,到其停下。

-131225
leftright

继续移动哨兵left,到其停下。

-131225
left,right

将基准数的位置与哨兵相遇处交换:

-121235
left,right

我们发现与总序列的排序后状况类似,我们继续迭代排序下去。

右序列中的左序列
2123

排序后:

1223

这样我们整个序列就排序完了。

原序列为:

-123-252-31

排序后为:

-3-2-112245

可以发现快速排序的每一遍处理就是将基准数归位,迭代下去,排序就完成了。

可以说快速排序是冒泡排序的改良版,它的最优以及平均时间复杂度为O(nlogn),但它遇到最坏的情况下时间复杂度仍和冒泡排序一样为O(n^2)。

这个是我用python写的可视化(可能看起来不太好)

# 橙色为排序基准数 黄色为右哨兵 青色为左哨兵 绿色为交换后的基准数位置

为什么要右哨兵先行?

我们可以修改代码看看左哨兵先行会发生什么。

修改后的排序值:-3 0 -1 5 7 (这里数组越界了所以7这个值出错了,pivot = a[5] 很显然越界)

很明显这出错了,下面我将排序过程写出来。

-1-3075
leftright

左哨兵先行排序过程

总序列

    QuickSort(array,0,num);        //        0为遍历的序列左端位置,num为序列右端位置

<1>左哨兵移动至停下

-1-3075
leftright

<2>右边哨兵移动至停下

-1-3075
left,right

<3>与基准数交换

0-3-175
left,right
左序列

        进入左序列递归 QuickSort(array,0,left-1); // left -1 =1

0-3-175
leftright

<1>左哨兵移动至停下

0-3-175
left,right

<2>与基准数交换

-30-175
left,right

        再次进入左序列递归 QuickSort(array,0,left-1); // left -1 =0

由于 0 == left - 1 ,退出本次的左序列递归。

进入右序列递归函数QuickSort(array,0+1,right);

由于 0+1 == right ,退出右序列递归。

右序列

        进入右序列递归 QuickSort(array,left+1,5); // left -1 =1

-30-175
leftright

<1>左哨兵移动至停下

-30-175
left,right

<2>与基准数交换

-30-157

结果为:

原序列-1-3075
左哨兵先行结果-30-157
右哨兵先行结果-3-1057

原因

我觉得原因是:

哨兵left移动后到达比基准数大的数位置,可能改位置后的还有一个数大于这个基准数,但left已经无法移动。

如果此时哨兵left所在位置右边都大于基准数的话,只能被迫等待right移动会面,把大于基准数的数交换到了前面。

例:

这是待排序序列:
1 0 3 7 2
哨兵left移动到3
1 0 3 7 2
    ^
下面移动哨兵right到3,然后与基准数交换
3 0 1 7 2
很显然这使得比基准数大的数交换到了左边。

如果是右哨兵先行的话就能保证交换的数是绝对比基准数小的。

当然这也是我们使用的基准数是第一位数才造成的问题,我们可以自己改动代码来解决问题。

代码

最后给大家奉上代码:

C

可以在dev上编译

#include <stdio.h>
int QuickSort(int a[],int left,int right){
	if(left >= right)
		return 0;			//到最末结束递归用 。 
	int pivot = a[left];	//基准数,用作被对比 。 
	int l =left,r =right;//l,r为哨兵即数组中位置标记 。 
	while(l < r){
		while(a[r] >= pivot && l < r )//右哨兵先行 。 
			r--;					
		while(a[l] <= pivot && l < r )
			l++;			
			//这两个while判定排序正确的话(即左侧数比基准数小,右侧数比基准数大),则将哨兵移动至下一位 
		if(l < r){					//两哨兵停下且未相遇,即左侧数比基准数大,右侧数比基准数小,交换即可 。 
			int temp = a[l];//这里我推荐写个swap(&a,&b);来实现
			a[l] = a[r];
			a[r] = temp;
		}
	}
	a[left]=a[l];
	a[l]=pivot;			//将基准数移至中间 。 
	QuickSort(a,left,l-1);
	QuickSort(a,l+1,right);//递归遍历左右 。 
}
void main(){
	int num=0,i=0;
	scanf("%d",&num);//输入数组成员个数 。 
	int array[num];
	
	for(i=0;i < num;i++)
		scanf("%d",&array[i]);//输入数组成员  。 
		
	QuickSort(array,0,num);//快速排序 。 
	
	for(i=0;i < num;i++)
		printf("%d ",array[i]);//打印排序后数组成员 。 
}

C++

可以在VS上编译

#include <iostream>
using namespace std;

int QuickSort(int a[], int left, int right) {
	if (left >= right)
		return 0;			//到最末结束递归用 。 
	int pivot = a[left];	//基准数,用作被对比 。 
	int l = left, r = right;//l,r为哨兵即数组中位置标记 。 
	while (l < r) {
		while (a[r] >= pivot && l < r)//右哨兵先行 。 
			r--;
		while (a[l] <= pivot && l < r)
			l++;
		//这两个while判定排序正确的话(即左侧数比基准数小,右侧数比基准数大),则将哨兵移动至下一位 
		if (l < r) {					//两哨兵停下且未相遇,即左侧数比基准数大,右侧数比基准数小,交换即可 。 
			int temp = a[l];    //这里我推荐写个swap(&a,&b);来实现
			a[l] = a[r];
			a[r] = temp;
		}
	}
	a[left] = a[l];
	a[l] = pivot;			//将基准数移至中间 。 
	QuickSort(a, left, l - 1);
	QuickSort(a, l + 1, right);//递归遍历左右 。 
}
void main() {
	int num = 0, i = 0;
		cin >> num;//输入数组成员个数 。 
		int* array = new int[num + 1];	//动态申请空间,gcc可以直接申请含变量的,VS的c语言可以用malloc来申请空间大小,calloc决定块。
	for (i = 0; i < num; i++)
		cin >> array[i + 1];//输入数组成员  。

	QuickSort(array, 0, num);//快速排序 。 

	for (i = 0; i < num; i++)
		cout << array[i + 1] <<" ";

	delete[] array;
}

Python

def QuickSort(arr, left, right):
    if left >= right:
        return None  # 到最末结束递归用 。
    pivot = arr[left]  # 基准数,用作被对比 。
    l = left
    r = right  # l,r为哨兵即数组中位置标记 。
    while l < r:
        while arr[r] >= pivot and l < r:  # 右哨兵先行 。
            r = r - 1
        while arr[l] <= pivot and l < r:
            l = l + 1  # 这两个while判定排序正确的话(即左侧数比基准数小,右侧数比基准数大),则将哨兵移动至下一位
        if l < r:  # 两哨兵停下且未相遇,即左侧数比基准数大,右侧数比基准数小,交换即可 。
            temp = arr[l]
            arr[l] = arr[r]
            arr[r] = temp  # swap操作
    arr[left] = arr[l]
    arr[l] = pivot  # 将基准数移至中间 。
    QuickSort(arr, left, l - 1)
    QuickSort(arr, l + 1, right)  # 递归遍历左右 。


array = input("输入序列,以空格分开成员。\n")
array = array.split(' ')  # 注意输入格式正确
array = [int(x) for x in array]

length = len(array)
print(f"length= {length}")

QuickSort(array, 0, length - 1)
print(array)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值