[算法入门]--十分钟弄懂快速排序

目录

一、荷兰国旗问题

1.问题描述:

2.思路:

3.合并实现

二、快速排序3.0(基准数随机产生)

三、快速排序思路

1.本质:

2.代码:

3.效果~


一、荷兰国旗问题

1.问题描述:

给定一个数组,随机在数组中选择一个元素,使得数组被分为三块区域,以被随机选择的元素为基准,左边的元素小于它,右边的元素大于它。

[算法入门]--十分钟弄懂快速排序_第1张图片


2.思路:

(1).我们人为划定一个界限,姑且叫他左边界吧,同理再划定一个右边界。

[算法入门]--十分钟弄懂快速排序_第2张图片

这个两个边界线的意义就是,下标<=边界线下标的元素小于3,下标>=边界线下标的元素大于3,中间位置在处理结束后都等于3。

(2).定义一个 i 指针从头遍历到尾部。

(3).每次 i 位置只会遇到三种情况:元素小于3,元素等于3,元素大于3。

如果元素小于3,把他放到left区域,将指针位置的元素和left下一位交换,left后移,指针后移。

如果元素等于3,把他放到中间区域,指针直接后移即可。

如果元素大于3,把他放到right区域。将指针位置元素和right前一位交换,right前移,指针不

动!!

不难发现,其实这就是一个两边区域向中间区域逼近的过程,但是要注意,遇到大于3的元素时,我们进行了这样一个操作:

将指针位置元素和有边界左边的元素交换,然后有边界扩大,目的是让这个大于3的元素被囊括到有边界>3区域中,但是,这时把一个从未遇到过的新元素扔到了指针的位置上,那么我们的 i 指针就不能后移了,继续进行下一次判断,没错就是判断这个数!!


边界判断:

1.指针超出数组范围,不搞了,摊牌走人。

2.指针跑到右边大于3的区域,那么此时 i 的工作肯定已经完成了,下班走人。


3.合并实现

1.随机函数randint

int randint(int a, int b) {
	return rand() % (b - a + 1) + a; 
}//熟悉的随机生成函数

 2.分区中

[算法入门]--十分钟弄懂快速排序_第3张图片

vector partition(vector& arr, int L, int R) {
		int i = L, left = L - 1, right = R + 1;		//定义左边界有边界 
		int target = arr[randint(L, R)];		//默认用最后一个元素选定中间值 
		vector res;
		res.push_back(0);
		res.push_back(0);
		while (i < right) {
			if (arr[i] < target) {
				swap(arr[i++], arr[left + 1]);
				left++;
			}
			else if (arr[i] > target) {
				swap(arr[i], arr[right - 1]);
				right--;
			}
			//注意↑扩展有边界时i不要移动 
			else i++;		//相等时指针后移 
		}
		res[0] = left + 1;
		res[1] = right - 1;
		return res;		
}//分区功能实现 

3.分区结束,将边界信息存入res临时数组并返回,用于快排中递归的设计。

[算法入门]--十分钟弄懂快速排序_第4张图片


二、快速排序3.0(基准数随机产生)

可以看到,我这里写的是快排3.0,那么可能会有人疑惑,凭什么就3.0了?

实际上,不是我吹,我这个快排,就是好。为什么呢?快排可以写出很多种,下面我列出几个版本:

1.快排1.0,每次都取R位置,也就是最右边的数作为基准数,将数组分为<=基准数和>基准数的两个区域。

2.快排2.0,同样也是取R位置为基准数,但是将数组分为了三个部分,类似荷兰国旗问题。


但是仔细分析,你会发现啊,他们虽然有一些微小的差别,但是最差的情况时间复杂度都是O(n^2),这种情况的原因:每次在的基准数都为数组的最右边的元素,那要是这个数就是最大的值,我快排还递归个锤子,不要面子了,和那些个选择,插入,冒泡有什么区别?

所以诞生了我们的快排3.0,只做了一行代码的改进,每次的基准数从数组中的全部元素中随机抽取。

你说妙不妙,这时从“平均”的角度来讲,就不存在所谓的最差情况,哈哈,根据我们的概率学,它的算法复杂度就衡(平均)为O(n lgn)。

 咳咳,不吹了,我们言归正传......


三、快速排序思路

1.定义一个partitionSort函数,传入数组的引用,左边界和右边界。


2.递归条件:如果左边界 < 右边界进行递归,否则只有一种情况,左边界等于右边界,我们返回,进行回溯即可。

3.递归部分设计:每次先将数组进行分区,分成三块。记录中间部分的下标,继续往左边和右边分别递归。

1.本质:

其实,通过这样的设计,我们可以窥透到快排的本质。那就是,每次让左边的数小于基准数,右边

的数大于基准数,此时左边子数组的最大数肯定是小于基准数的,同样,右边数的最小数也肯定大

于基准数,那么至少,以基准数为中心的三个数已经有序。那么继续重复,子数组的子数组右边最

大值又小于它的最小数......周而复始,最终根据我们的极限思维,数组一定会达到一个有序的状

态。嗯,不要问我为什么,不能再解释了。


2.代码:

#include 
#include 
#include 
#include 
using namespace std;
/*小功能*/ 
int randint(int a, int b) {
	return rand() % (b - a + 1) + a; 
}//熟悉的随机生成函数
void initArr(vector& arr, int N) {
	for (int i = 0; i < N; i++) arr.push_back(randint(1, 100));
}//随机生成数组 
void coutArr(vector& arr) {
	vector::iterator it = arr.begin();
	while (it < arr.end()) {
		cout << *it << ' ';
		it++;
	}cout << endl;
}//打印数组 
/*小功能*/ 

/*快排实现部分*/ 
vector partition(vector& arr, int L, int R) {
		int i = L, left = L - 1, right = R + 1;		//定义左边界有边界 
		int target = arr[randint(L, R)];		//默认用最后一个元素选定中间值 
		vector res;
		res.push_back(0);
		res.push_back(0);
		while (i < right) {
			if (arr[i] < target) {
				swap(arr[i++], arr[left + 1]);
				left++;
			}
			else if (arr[i] > target) {
				swap(arr[i], arr[right - 1]);
				right--;
			}
			//注意↑扩展有边界时i不要移动 
			else i++;		//相等时指针后移 
		}
		res[0] = left + 1;
		res[1] = right - 1;
		return res;		
}//分区功能实现 

void partitionSort(vector& arr, int L, int R) {
	if (L < R) {
		vector res;
		res = partition(arr, L, R);
		partitionSort(arr, L, res[0] - 1);			 
		partitionSort(arr, res[1] + 1, R);
	}
}//快排递归部分 
/*快排实现部分*/ 
int main() {
	srand(time(0));			//不必多说,随机种子来! 
	vector arr;
	initArr(arr, 20);
	cout << "Original array:" << endl;
	coutArr(arr);
	partitionSort(arr, 0, arr.size() - 1);
	cout << "Processed array:" << endl;
	coutArr(arr);
} 

3.效果~

[算法入门]--十分钟弄懂快速排序_第5张图片


你可能感兴趣的:(蓝桥杯,算法竞赛入门,排序算法,算法,c++)