快速排序(C语言)

本文深入探讨了快速排序算法,分析了枢纽元选取的重要性,并提出了两种有效的选取策略:随机选取和三数中位数选取,避免了最坏情况的发生。同时,介绍了安全的分割策略和完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

快速排序与归并排序一样也是一种分治递归算法,其排序(升序)步骤可以简述如下:
1、在待排元素集合N中任选一元素做为枢纽元
2、开始分割元素,使左部元素均小于枢纽元,右部元素均大于枢纽元
3、对左右子序列递归应用步骤1、2

1、选取枢纽元

有一种极其常见的、错误的、非常糟糕的枢纽元的选取,是选取数组第一个或者最后一个元素为枢纽元,如果输入序列是随机的那么上述做法可以接受,但如果输入的序列与我们要排的序列正好相反,那么在分割元素阶段就会出现所有元素被划分到左半部或者右半部,更糟糕的是,在其后续递归过程中也是如此,那么其排序花费的时间将是二次的,因此该种枢纽元的选取应该摒弃。

以下是一种错误的枢纽元选取的例程(尽管结果是对的):

void swap(int *p_num, int k, int j)
{
         if (NULL == p_num) {
                 return;
         }
         int tmp  = p_num[k];
         p_num[k] = p_num[j];
         p_num[j] = tmp;
}
 
void quick_sort(int * p_num, int left, int right)
{
         if ((NULL == p_num) || left >= right) {
                 return;
         }
         int last = left;
         swap(p_num, left, (left + right) / 2);
         for (int i = left + 1; i <= right; i++) {
                 if (p_num[i] < p_num[left]/* 直接将最左边的元素作为枢纽元 */) {
                         swap(p_num, ++last, i);
                 }
         }
         swap(p_num, left, last);
         quick_sort(p_num, left, last - 1);
         quick_sort(p_num, last + 1, right);
}

那么我们该如何选取枢纽元呢?有两种常见的做法,一种是使用随机数生成器随机选取(但我们要注意到随机数的选取是昂贵的),另一种做法是选取3个数的中位数,这种选取方式总的来说是好的稳妥的,如下:

void Swap(int *A, int *B)
{
	int Tmp = *A;
	*A = *B;
	*B = Tmp;
}
int median3(int nums[], int left, int right)
{
	int center = (left + right) / 2;
	if (nums[left] > nums[center]) {
		Swap(&nums[left], &nums[center]);
	}
	if (nums[left] > nums[right]) {
		Swap(&nums[left], &nums[right]);
	}
	if (nums[center] > nums[right]) {
		Swap(&nums[center], &nums[right]);
	}

	Swap(&nums[center], &nums[right]); // 隐藏枢纽元

	return nums[right];
}

2、分割策略

分割所做的事就是将小元素移动到数组左边,大元素移动到数组右边,一种安全稳妥的分割策略是,选取枢纽元之后将其与最后一个元素交换,然后对第0个元素和倒数第二个元素分割,因为没有必要对枢纽元进行分割,故将其移动到最后进行隐藏,具体分割步骤如下:
P1
P2
上述分割策略摘自数据结构与算法分析第二版page180

3、代码实现

#define Cutoff (3)
void Swap(int *A, int *B)
{
	int Tmp = *A;
	*A = *B;
	*B = Tmp;
}

void select_sort(int *p_arr, const int size)
{
	assert(p_arr != NULL);
	for (int i = 0; i < size; i++) {
		for (int j = i + 1; j < size; j++) {
			if (*(p_arr + i) > *(p_arr + j)) {
				Swap(p_arr + i, p_arr + j);
			}
		}
	}
}

void qsort(int nums[], int left, int right)
{
	int i = 0, j = 0, pivot = 0;

	if (left + Cutoff <= right) {
		pivot = median3(nums, left, right);
		i = left;
		j = right;
		for (;;) {
			while (nums[++i] < pivot) { }
			while (nums[--j] > pivot) { }
			if (i < j) {
				Swap(&nums[i], &nums[j]);
			}
			else {
				break;
			}
		}
		Swap(&nums[i], &nums[right]);

		qsort(nums, left, i - 1);
		qsort(nums, i + 1, right);
	}
	else {
	    // 不满3个元素进行选择排序
		select_sort(nums + left, right - left + 1);
	}
}

void quicksort(int nums[], int size)
{
	qsort(nums, 0, size - 1);
}

参考文献

数据结构与算法分析第二版

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值