快速排序(C语言)

快速排序(英语:Quicksort),又称分区交换排序,简称「快排」,是一种被广泛运用的排序算法。

快速排序的工作原理是通过分治的方式来将一个数组排序。

快速排序分为三个过程:

  1. 将数列划分为两部分(要求保证相对大小关系);
  2. 递归到两个子序列中分别进行快速排序;
  3. 不用合并,因为此时数列已经完全有序。

对于完成一个快排函数,需要传入的参数分别是需要排序的数组a,最左边数据的下标以及最右边数据的下标


第一种方法:交换法

而实现交换的函数比较简单,这里不过多赘述。

void Swap(int* a,int* b) {
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

这一方法的过程是:

先right从右向左开始遍历,right向左走,如果遇到比a[key]更加小的值就停下来;

再left从左向右开始遍历,left开始向右走,相反在遇到比a[key]更加大的值就停下来;

将数组中的两个值作交换;

right再重新开始继续向左走,不断重复这一过程直到二者相遇;

相遇后将相遇处数组的值与a[key]交换,同时将key的值改为相遇处的下标;

向下递归。

如图,举个简单的例子。

void QuickSort(int* a, int left, int right) {

	if (left >= right) {//放在最前
		return; 
 	}
	int begin = left, end = right;//left与right 会被改变,用两个变量保留它们的值用于接下来的递归

	int key = left;

	while (left < right) {

		while (left < right && a[right] >= a[key]) {
			right--;
		}
		 
		while (left < right && a[left] <= a[key]) {
			left++;
		}
		Swap(&a[left], &a[right]);

	}

	Swap(&a[left], &a[key]);
	key = left;

	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);
}

在测试一下结果:

没有问题。

然而,以这种方法在进行对一个十分接近有序的数组进行排序时,会出现一个问题。

 即因为 key 值很小 right 一直向右 直接与left相遇 每次递归都将全部 n 个数据遍历 并且一次也只排好了一个值 。 如此一来大大降低了效率。

对于这种情况,我们可以进行一些优化。

一种方法是找随机值

取随机值法让key取随机值,再把随机值交换到首位

通过   rand()% (right - left + 1) 取到在left 到 right 的一个随机值

//取随机值法让key取随机值,再把随机值交换到首位
int random = rand()% (right - left + 1);
random = random + left;
Swap(&a[left], &a[random]);

random = random + left的原因在递归时是存在left不为0的情况。

另一种方法是三值取中

int Getmid(int* arr,int l,int r) {//需要return 的是下标
	int m = (r + l) / 2;
	int a = arr[l];
	int b = arr[r];
	int c = arr[m];

	if (a >= b && b > c ) {//注意a,b,c中存在相等的情况
		return r;
	}		
	else if (a >= c && c > b) {
		return m;
	}
	else if (c >= a && a > b) {
		return l;
	}	
	else if (b >= a && a > c) {
		return l;
	}
	else if (b >= c && c > a) {
		return m;
	}
	else if (c >= b && b > a) {
		return r;
	}
}

 在数组的第一个,最后一个与中间的一个数据中取出中值,再把其交换到首位

优化后如下

//交换法的快速排序
void QuickSort(int* a, int left, int right) {

	if (left >= right) {//放在最前
		return; 
 	}

	//小区间优化,区间较小时,使用插入排序减少递归次数

	int begin = left, end = right;//left与right 会被改变,用两个变量保留它们的值用于接下来的递归

	//取随机值法让key取随机值,再把随机值交换到首位
	//int random = rand()% (right - left + 1);
	//random = random + left;
	//Swap(&a[left], &a[random]);

	//三值取中法
	int mid = Getmid(a,left,right);
	//printf("%d\n", a[mid]);
	Swap(&a[left], &a[mid]);
	
	int key = left;

	while (left < right) {
		while (left < right && a[right] >= a[key]) {
			right--;
		}	 
		while (left < right && a[left] <= a[key]) {
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[key]);
	key = left;

	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);
}

第二种方法:挖坑法

代码如下:

//挖坑法
void QuickSort2(int* a, int left, int right) {
	
	if (left >= right) {//放在最前
		return;
	}

	int begin = left, end = right;//left与right 会被改变,用两个变量保留它们的值用于接下来的递归
	int key = left;
	int tmp = a[key];//a[left]在第一次放入左边的坑时发生改变
	while (left < right) {

		while (left < right && a[right] >= tmp) {
			right--;
		}
		a[left] = a[right];//从右边开始 找比a[key]小的值,找到了之后 把值放进前一个坑 在此位置在挖一个坑
		while (left < right && a[left] <= tmp) {
			left++;
		}
		a[right] = a[left]; //从左边开始 找比a[key]大的值,找到了之后 把值放进放进前一个坑 在此位置在挖一个坑
	}
	a[left] = tmp;
	key = left;

	QuickSort2(a, begin, key - 1);
	QuickSort2(a, key + 1, end);

}

 第三种方法:双指针法

 双指针法总体上是:当cur 指针指向的数 小于a[key] 时先prev++,再把cur与prev指向的数交换,cur再++;当cur 指针指向的数 小于a[key] 时,cur++。

 

这种方法较前两种更为简单。

//双指针法的快速排序     cur >= key  cur++    ;    cur < key , prev++  cur++  ;
void QuickSort3(int* a, int left, int right) {
	if (left >= right) {
		return;
	}
	int key = left;
	int prev = left, cur = left + 1 ;
	while (cur <= right) {
		if (a[cur] < a[key]) {
			++prev;
			Swap(&a[prev], &a[cur]);
		}

		cur++;
	}
	Swap(&a[key], &a[prev]);
	key = prev;
	
	QuickSort3(a, left, key - 1);
	QuickSort3(a, key + 1, right);

}

(本人资历尚浅,如果文中有任何问题,劳请指出。)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值