快速排序的多种实现方式


快排

一趟排序将要序列分割成独立的两部分,一部分的所有数据都比另外一部分的所有数据都要小,按此方法再对这两部分数据分别进行快速排序,整个排序过程可以递归进行。

void quickSort(vector<int> &a, int beg, int end){
	if (beg >= end)	return;

	auto q = partition(a, beg, end); 
	quickSort(a, beg, q-1);
	quickSort(a, q+1, end);
}

数据分割的过程需要一个分界值,作为两部分的"分界",一般取序列第一个或者最后一个。有了分界值后,如何将序列划分为两部分就是快排的核心问题,也就是如何设计上述的partition函数。

双指针法

假设序列为a, a.size() == n, 初始输入上下界即为low = 0,high = n-1 , 双指针分别用i, j表示:

  • 选取分界值pivot = a[high]

  • i = low, j = high(分别是待处理序列(不包括分界值a[n])的首尾元素下标)

  • i往右走,直到:a[i] > key,这儿一定是i先走,下一步j再走。
    j往左走,直到:a[j] < key
    然后交换a[i]a[j]

  • 循环上一步直到ij相遇,最后将a[high]a[i]交换。

直接贴代码吧:

// 双指针(pivot为末尾)
int partition1_1(vector<int> &a, int low, int high){
	auto pivot = a[high];
	int i = low, j = high;
	while (i < j){
		// 先看左边(顺序不能错)
		while (i < j && a[i] <= pivot)	++i;
		// 再看右边
		while (i < j && a[j] >= pivot)	--j;
		swap(a[i], a[j]);
	}
	swap(a[high], a[i]);
	return i;
}

如果选取的分界值pivot = a[low],那么代码少许不同,一开始我也没有注意到:

// 双指针(pivot为开头)
int partition1_2(vector<int> &a, int low, int high){
	auto pivot = a[low];
	int i = low, j = high;
	while (i < j){
		// 这儿是j先走,先看右边
		while (i < j && a[j] >= pivot)	--j;
		// 再看左边
		while (i < j && a[i] <= pivot)	++i;
		swap(a[i], a[j]);
	}
	swap(a[low], a[i]);
	return i;
}

不知道叫啥名?

用已处理区间与未处理区间的方式来理解:

假设序列为a, a.size() == n, 初始输入上下界即为low = 0,high = n-1, 双指针分别用i, j表示::

  • 选取分界值pivot = a[high]

  • i = low, j = low(开始时已处理区间为空,i一直指向未处理区间的第一个元素,也就是已处理区间的下一个元素,j则用来进行遍历

  • 最后pivot与已处理区间的后面那个元素(i)交换即可。

贴个图理解:
在这里插入图片描述

// 单边法(末尾为pivot)
int partition2_1(vector<int> &a, int low, int high){
	auto pivot = a[high];
	int i = low, j = low;//i指向以处理区间右边界,j用来遍历元素
	while (j < high){
		if (a[j] < pivot)
			swap(a[i++], a[j]); //已处理区间多了一个元素,右边界增加1
		++j;
	}
	swap(a[high], a[i]);
	return i;
}
//换种写法(pivot同样为末尾)
int partition2(vector<int> &a, int low, int high){
   auto pivot = a[high];
   int i = low;
   for (int j = low; j < high; ++j){
   	if (a[j] < pivot)
   		swap(a[j], a[i++]);
   }
   swap(a[i], a[high]);
   return i;
}

如果以开头作为pivot:

// 单边法(开头为pivot)
int partition2_2(vector<int> &a, int low, int high){
   auto pivot = a[low];
   int i = low+1, j = low+1;//i指向以处理区间右边界,j用来遍历元素
   while (j <= high){
   	if (a[j] < pivot)
   		swap(a[i++], a[j]);//已处理区间多了一个元素,右边界增加1
   	++j;
   }
   swap(a[low], a[i-1]);
   return i-1;
}

三数取中法

数据结构与算法之美:14 | 排序优化:如何实现一个通用的、高性能的排序函数

关于三数取中法的理解,可以看这个:快速排序之三数取中法,思想还是很简单的,实现的时候需要注意细节。

如果数据原来就是有序的或者接近有序的,每次分区点都选择最后一个数据,那快速排序算法就会变得非常糟糕,时间复杂度就会退化为 O(n^2)。这种 O(n^2) 时间复杂度出现的主要原因还是因为分区点选的不够合理

最理想的分区点是:被分区点分开的两个分区中,数据的数量差不多

比较常用、比较简单的分区算法就是三数取中法随机法

void getMid(vector<int> &a, int low, int high){
	auto mid = (high + low) / 2;
	if (a[low] > a[mid])	swap(a[low], a[mid]);
	if (a[low] > a[high])	swap(a[low], a[high]);
	if (a[mid] > a[high])	swap(a[mid], a[high]);
	swap(a[mid], a[high-1]);
}
int partition3(vector<int> &a, int low, int high){
	getMid(a, low, high);
	auto pivot = a[high-1];
	//i, j一定从low开始,如果从low+1开始只有两个元素时会出错
	int i = low, j = low;//i指向以处理区间右边界,j用来遍历元素
	while (j < high-1){
		if (a[j] < pivot)
			swap(a[i++], a[j]);//已处理区间多了一个元素,右边界增加1
		++j;
	}
	swap(a[high-1], a[i]);
	return i;
}

随机法

我们每次选取pivot之前随机选择一个元素与末尾元素交换,然后再以末尾元素作为pivot:

    int randomPartition(vector<int>& a, int low, int high) {
        int i = rand() % (high - low + 1) + low; //随机一个i,i在区间[low, high]
        swap(a[i], a[high]);
        return partition(a, low, high);
    }
    int partition(vector<int> &a, int low, int high){
        auto pivot = a[high];
        int i = low, j = low;
        while(j < high){
            if(a[j] > pivot){ //这儿选择将比pivot大的放在左边
                swap(a[j], a[i]);
                ++i;
            }
            ++j;
        }
        swap(a[i], a[high]);
        return i;
    }

测试代码:

#include <iostream>
#include <vector>

using namespace std;

// 双指针(pivot为末尾)
int partition1_1(vector<int> &a, int low, int high){
	auto pivot = a[high];
	int i = low, j = high;
	while (i < j){
		// 先看左边(顺序不能错)
		while (i < j && a[i] <= pivot)	++i;
		// 再看右边
		while (i < j && a[j] >= pivot)	--j;
		swap(a[i], a[j]);
	}
	swap(a[high], a[i]);
	return i;
}

// 双指针(pivot为开头)
int partition1_2(vector<int> &a, int low, int high){
	auto pivot = a[low];
	int i = low, j = high;
	while (i < j){
		while (i < j && a[j] >= pivot)	--j;
		while (i < j && a[i] <= pivot)	++i;
		swap(a[i], a[j]);
	}
	swap(a[low], a[i]);
	return i;
}

// 单边法(末尾为pivot)
int partition2_1(vector<int> &a, int low, int high){
	auto pivot = a[high];
	int i = low, j = low;//i指向以处理区间右边界,j用来遍历元素
	while (j < high){
		if (a[j] < pivot)
			swap(a[i++], a[j]);//已处理区间多了一个元素,右边界增加1
		++j;
	}
	swap(a[high], a[i]);
	return i;
}

// 单边法(开头为pivot)
int partition2_2(vector<int> &a, int low, int high){
	auto pivot = a[low];
	int i = low+1, j = low+1;//i指向以处理区间右边界,j用来遍历元素
	while (j <= high){
		if (a[j] < pivot)
			swap(a[i++], a[j]);//已处理区间多了一个元素,右边界增加1
		++j;
	}
	swap(a[low], a[i-1]);
	return i-1;
}

void quicksort(vector<int> &a, int low, int high){
	if (low >= high) return;
	auto q = partition2_2(a, low, high);
	quicksort(a, low, q-1);
	quicksort(a, q+1, high);
}

int main()
{
	vector<int> a{2,5,1,4,3,0,9,7};
	int n = a.size();
	quicksort(a, 0, n-1);
	for (const auto &c : a) cout << c << " ";
	cout << endl;
	return 0;
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值