快速排序及改进

快速排序基本思路:找到一个标定点,左边的元素小于标定点,右边元素大于标定点,然后再对左右区间递归快速排序。

// 对arr[l...r]部分进行partition操作
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
template <typename T>
int __partition(T arr[], int l, int r) {

	T v = arr[l];

	int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
	for (int i = l + 1; i <= r; i++)
		if (arr[i] < v) {
			j++;
			swap(arr[j], arr[i]);
		}

	swap(arr[l], arr[j]);

	return j;
}

// 对arr[l...r]部分进行快速排序
template <typename T>
void __quickSort(T arr[], int l, int r) {

	if (l >= r)
		return;

	int p = __partition(arr, l, r);
	__quickSort(arr, l, p - 1);
	__quickSort(arr, p + 1, r);
}

template <typename T>
void quickSort(T arr[], int n) {

	__quickSort(arr, 0, n - 1);
}

改进1:如果数组近乎有序,标定点的选择会影响快速排序的性能,如果每次都选择最小值作为标定点,快速排序时间复杂度会退化为O(n^{2}),因此需要随机化标定点元素。

template <typename T>
int __partition(T arr[], int l, int r) {
	// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
	swap(arr[l], arr[rand() % (r - l + 1) + l]);
	T v = arr[l];

	int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
	for (int i = l + 1; i <= r; i++)
		if (arr[i] < v) {
			j++;
			swap(arr[j], arr[i]);
		}

	swap(arr[l], arr[j]);

	return j;
}

改进2:如果数组有大量相同元素,上面的做法会将相同元素分到大于v的区间,会造成两个子区间元素不平衡,所以需要将相等元素平衡地分入两个区间。

template <typename T>
int _partition2(T arr[], int l, int r) {

	// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
	swap(arr[l], arr[rand() % (r - l + 1) + l]);
	T v = arr[l];
	
	//[l+1,i) <=v, (j,r]>=v
	int i = l + 1, j = r;
	while (i <j)
	{
		while (i <=r&&arr[i] < v)
			i++;
		while (j >l && arr[j-1] > v)
			j--;
		swap(arr[i], arr[j]);
		i++; j--;
	}
	swap(arr[i], arr[l]);
	return i;
}

对应地,还有三路快速排序算法。

快速排序算法有多种改进方法,以下为你介绍常见的几种: - **三数取中法**:在快速排序中,基准值的选择对算法性能影响较大。若基准值选择不当,可能导致分割的两部分数据极度不平衡,使算法性能下降。三数取中法通过选取数组首、尾和中间位置的三个元素,将这三个元素排序后取中间值作为基准值,这样能让基准值更接近数组的中间大小,使分割更平衡。例如,在处理数组`[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]`时,选取`3`、`5`和`2`这三个元素,排序后为`2`、`3`、`5`,取中间值`3`作为基准值。 - **小数组使用插入排序**:当数组长度较小时,快速排序的递归调用开销较大,而插入排序在小规模数据上性能较好。因此,当子数组长度小于某个阈值(如 10)时,使用插入排序来处理这些小数组,能减少递归调用次数,提高算法效率。如在快速排序过程中,当某个子数组长度为 8 时,就采用插入排序对其进行排序。 - **挖坑法**:挖坑法是一种改进的分区方式。它先选取一个基准值,将其位置作为初始的“坑”,然后从数组两端向中间扫描,把符合条件的元素填入“坑”中,并产生新的“坑”,直到左右指针相遇,最后将基准值填入最终的“坑”中。以下是挖坑法实现快排的代码示例: ```cpp template<class T> // 插入排序 void CC_InsertSort(T* begin, int Len) { T t, * A = begin; int i, j; for (i = 1; i < Len; i++) if (A[i] < A[i - 1]) { t = A[i]; for (j = i - 1; j >= 0 && A[j] > t; j--) A[j + 1] = A[j]; A[j + 1] = t; } } template<class T> // 挖坑快速排序 int partition(T* begin, int Len) { T* A = begin; T Tem = A[0]; // 选取比较的基准,其位置也就是初始的坑位 int i = 0, j = Len - 1; while (i < j) { while (A[j] >= Tem && i < j) j--; if (i < j) A[i++] = A[j]; while (A[i] <= Tem && i < j) i++; if (i < j) A[j--] = A[i]; } A[j] = Tem; return j; } template<class T> void CC_QuickSort(T* A, int Len) { if (Len <= 1) return; int k = partition(A, Len); CC_QuickSort(A, k); CC_QuickSort(&A[k + 1], Len - k - 1); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值