排序算法总结

1、堆排序

//调整大顶堆,从非叶节点节点开始从上到下,即从父节点到子节点
void adjustHeap(vector<int> &nums, int i, int length) {
	int temp = nums[i];//先取出当前元素i
	for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {//从i节点的左子节点开始,也就是2i+1处开始
		if (k + 1 < length && nums[k] < nums[k + 1]) {//如果左子节点小于右子节点,k指向左子节点
			k++;                                      //比较左右节点的目的在于,我找个一大的子节点跟父节点比较就好啦,不用两个都比较
		}
		if (nums[k] > temp) {//如果子节点大于父节点,将子节点赋值给父节点(不用进行交换)
			nums[i] = nums[k];
			i = k;//节点i也相应的向下移动
		}
		else {
			break;
		}
	}
	nums[i] = temp;//将temp值放到最终位置
}
void HeapSort(vector<int> &nums) {
	//构建大顶堆,开始的位置一定是第一个非叶节点,所以
	for (int i = int(nums.size() / 2) - 1; i >= 0; i--) {//从第一个非叶节点开始调整,总的节点个数为n=nums.size(),父节点为n/2-1;
		adjustHeap(nums, i, nums.size());      //i--就是从右边到左边
	}
	//调整堆结构+交换栈顶元素与末尾元素
	for (int j = int(nums.size() - 1); j > 0; j--) {
		swap(nums[0], nums[j]);//将栈顶元素与末尾元素进行交换,然后数组减小
		adjustHeap(nums, 0, j);//重新对堆进行调整,这个时候为什么不从第一个非叶节点开始呢?因为只是将调整好的大顶堆的根移动到最底部,
		                      //这个时候,根节点不符合规则,所以从0开始就可以。
	}
}

2、快速排序,递归版本




void QuickSort(vector<int> &nums, int left, int right) {
	int i = left, j = right,base;
	if (left > right) {
		return;
	}
	base = nums[left];
	while (i != j) {
		while (i < j && nums[j] >= base) {
			j--;
		}
		while (i < j&& nums[i] <= base) {
			i++;
		}
		swap(nums[i], nums[j]);
	}
	nums[left] = nums[i];
	nums[i] = base;
	QuickSort(nums, left, i - 1);
	QuickSort(nums, i + 1, right);
}
/*
2、快排非递归,其实就是用栈保存左右边界,和回归类似
*/
int qSortHelp(vector<int> &nums,int left,int right){
	int i = left, j = right, base;
	
	base = nums[left];
	while (i != j) {
		while (i < j && nums[j] >= base) {
			j--;
		}
		while (i < j&& nums[i] <= base) {
			i++;
		}
		swap(nums[i], nums[j]);
	}
	nums[left] = nums[i];
	nums[i] = base;
	return i;
}
void QuickSort2(vector<int> &nums) {
	if (nums.empty()) {
		return;
	}
	stack<int> st;
	int pos = 0;
	int left = 0, right = (int)nums.size() - 1;
	st.push(left);
	st.push(right);
	while (!st.empty()) {
		right = st.top();
		st.pop();
		left = st.top();
		st.pop();
		pos = qSortHelp(nums, left, right);
		if (pos + 1 < right) {
			st.push(pos + 1);
			st.push(right);
		}
		if (pos - 1 > left) {
			st.push(left);
			st.push(pos - 1);
		}
	}
	return;
}

快速排序的优化,多数的优化是对枢轴选取的优化

/*
1、三数取中法
如果代拍序列为:8 1 4 9 6 3 5 2 7 0
左边为:8,右边为0,中间为6
我们这里取三个数排序后,中间那个数作为枢轴,则枢轴为6
在选取中轴值时候,可以从左中右三个中选取扩大到五个元素中或者更多元素中选取,一般的,会有(2t+1)平均分区法
(median-of-(2t+1),三平均分区法英文为median-of-three
代码如下:
*/
int selectPosMidThree(vector<int> &nums, int left, int right) {
	int mid = left + (right - left) >> 1;//计算数组中间的元素的下标
	//使用三数取中法选择枢轴
	if (nums[mid] > nums[right]) {//目标: nums[mid] <= nums[right]
		swap(nums[mid], nums[right]);
	}
	if (nums[left] > nums[right]) {//目标: nums[low] <= nums[right]
		swap(nums[left], nums[right]);
	}
	if (nums[mid] > nums[left]) {//目标: nums[left] >= nums[mid]
		swap(nums[mid], nums[left]);
	}
	//就是通过比较他们的大小,把中间大小的数字,放到nums[low]中,也就是头部枢轴要换成中间大小的数
	//此时,nums[mid] <= nums[low] <= nums[high]
	return nums[left];
	//low的位置上保存这三个位置中间的值
	//分割时可以直接使用low位置的元素作为枢轴,而不用改变分割函数了
}

/*
2、当待排序序列的长度分割到一定大小后,使用插入排序
原因:对于很小和部分有序的数组,快排不如插入排序好。当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差此时可以使用插排而不是快排
截止范围:待排序序列长度N=10,虽然在5-20之间任一机制范围都有可能产生类似的结果,这种做法也避免了一些有害的退化情形。
*/
void sorts(vector<int> &nums, int left, int right) {
	if (left - right + 1 < 10) {
		//InsertSort(nums, left, right);
		return;
	}
	else {
		//快排代码	
	}
}

/*
3、在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用在对key相等元素分割
举例:

待排序序列 1 4 6 7 6 6 7 6 8 6

三数取中选取枢轴:下标为4的数6

转换后,待分割序列:6 4 6 7 1 6 7 6 8 6

枢轴key:6

本次划分后,未对与key元素相等处理的结果:1 4 6 6 7 6 7 6 8 6

下次的两个子序列为:1 4 6 和 7 6 7 6 8 6

本次划分后,对与key元素相等处理的结果:1 4 6 6 6 6 6 7 8 7

下次的两个子序列为:1 4 和 7 8 7

经过对比,我们可以看出,在一次划分后,把与key相等的元素聚在一起,能减少迭代次数,效率会提高不少

具体过程:在处理过程中,会有两个步骤

第一步,在划分过程中,把与key相等元素放入数组的两端

第二步,划分结束后,把与key相等的元素移到枢轴周围

举例:

待排序序列 1 4 6 7 6 6 7 6 8 6

三数取中选取枢轴:下标为4的数6

转换后,待分割序列:6 4 6 7 1 6 7 6 8 6

枢轴key:6

第一步,在划分过程中,把与key相等元素放入数组的两端

结果为:6 4 1 6(枢轴) 7 8 7 6 6 6

此时,与6相等的元素全放入在两端了

第二步,划分结束后,把与key相等的元素移到枢轴周围

结果为:1 4 66(枢轴) 6 6 6 7 8 7

此时,与6相等的元素全移到枢轴周围了

之后,在1 4 和 7 8 7两个子序列进行快排

void QSort(int arr[], int low, int high)
{
	int first = low;
	int last = high;

	int left = low;
	int right = high;

	int leftLen = 0;
	int rightLen = 0;

	if (high - low + 1 < 10)
	{
		//InsertSort(arr, low, high);
		return;
	}

	//一次分割
	//int key = SelectPivotMedianOfThree(arr, low, high);//使用三数取中法选择枢轴

	while (low < high)
	{
		while (high > low && arr[high] >= key)
		{
			if (arr[high] == key)//处理相等元素
			{
				swap(arr[right], arr[high]);
				right--;
				rightLen++;
			}
			high--;
		}
		arr[low] = arr[high];
		while (high > low && arr[low] <= key)
		{
			if (arr[low] == key)
			{
				swap(arr[left], arr[low]);
				left++;
				leftLen++;
			}
			low++;
		}
		arr[high] = arr[low];
	}
	arr[low] = key;

	//一次快排结束
	//把与枢轴key相同的元素移到枢轴最终位置周围
	int i = low - 1;
	int j = first;
	while (j < left && arr[i] != key)
	{
		swap(arr[i], arr[j]);
		i--;
		j++;
	}
	i = low + 1;
	j = last;
	while (j > right && arr[i] != key)
	{
		swap(arr[i], arr[j]);
		i++;
		j--;
	}
	QSort(arr, first, low - 1 - leftLen);
	QSort(arr, low + 1 + rightLen, last);
}
*/

4、优化归并操作
快排函数在函数尾部有两次递归操作,我们可以对其使用尾递归优化
优点:如果待排序的序列划分极端不平衡,递归的深度将趋近于n,而栈的大小是很有限的,每次递归调用都会耗费一定的栈空间,
函数的参数越多,每次递归耗费的空间也越多。优化后,可以缩减堆栈深度,由原来的O(n)缩减为O(logn),将会提高性能。

void QSort(int arr[], int low, int high)
{
	int pivotPos = -1;
	if (high - low + 1 < 10)
	{
		//InsertSort(arr, low, high);
		return;
	}
	while (low < high)
	{
		//pivotPos = Partition(arr, low, high);
		//QSort(arr, low, pivot - 1);
		//low = pivot + 1;
	}
}

/
/

5、使用并行或多线程处理子序列(略)
*/

3、选择排序,时间复杂度n的平方,不稳定

void selectSort(vector<int> &nums) {
	int n = nums.size();
	for (int i = 0; i < n; i++) {
		int index = i;
		for (int j = i; j < n; j++) {
			if (nums[j] < nums[index]) {
				index = j;
			}
		}
		swap(nums[i], nums[index]);
	}
	return;
}

4、插入排序
性质:1、时间复杂度:O(n2) 2、空间复杂度:O(1) 3、稳定排序 4、原地排序

void insertSort(vector<int> &nums) {
	if (nums.empty()) {
		return;
	}
	int n = nums.size();
	for (int i = 1; i < n; i++) {
		int j = i;
		while (j-1 >= 0) {
			if (nums[j] < nums[j - 1]) {
				swap(nums[j], nums[j - 1]);
				j--;
			}
			else {
				break;
			}
		}
	}
}

5、冒泡排序

void mSort(vector<int> &nums) {
	if (nums.empty()) {
		return;
	}
	int n = nums.size();
	for (int i = 0; i < n; i++) {
		for (int j = 0; j+1 < n-i; j++) {//交换一次,其中一个有序
			if (nums[j] > nums[j + 1]) {
				swap(nums[j], nums[j + 1]);
			}
		}
	}
	return;
}
void mSort2(vector<int> &nums) {
	if (nums.empty()) {
		return;
	}
	int n = nums.size();
	bool flag = true;
	for (int i = 0; i < n; i++) {
		flag = true;
		for (int j = 0; j + 1 < n - i; j++) {//交换一次,其中一个有序
			if (nums[j] > nums[j + 1]) {
				swap(nums[j], nums[j + 1]);
				flag = false;
			}
		}
		if (flag) {
			break;
		}
	}
}

6、希尔排序

  • 将arr[i]插入到所在分组的正确位置上
  • arr[i]] 所在的分组为 … arr[i-2*h],arr[i-h], arr[i+h] …
    性质:1、时间复杂度:O(nlogn) 2、空间复杂度:O(1) 3、非稳定排序 4、原地排序
void insertI(vector<int> &nums, int d, int i) {//插入排序,间隔为d
	int temp = nums[i];
	int k;
	for (k = i; k-d>=0; k = k - d) {
		if (nums[k] < nums[k - d]) {
			swap(nums[k], nums[k - d]);
		}
		else {
			break;
		}
	}
}

void shellSort(vector<int> &nums) {
	if (nums.empty()) {
		return;
	}

	int n = nums.size();
	// 对每组间隔为 h的分组进行排序,刚开始 h = n / 2;
	for (int d = n / 2; d > 0; d /= 2) {
		//对各个局部分组进行插入排序
		for (int i = d; i < n; i++) {
			// 将nums[i] 插入到所在分组的正确位置上
			insertI(nums, d, i);//从i开始一直到0
			//需要注意的是,对各个分组进行插入的时候并不是先对一个组排序完了再来对另一个组排序,而是轮流对每个组进行排序。
		}
	}
}

7、归并排序
性质:1、时间复杂度:O(nlogn) 2、空间复杂度:O(n) 3、稳定排序 4、非原地排序

void mergeHelp(vector<int> &nums, int left,int mid, int right) {
	int i = left, j = mid + 1,index=0;
	vector<int> temp(right - left + 1);
	while (i <= mid && j <= right) {
		if (nums[i] < nums[j]) {
			temp[index++] = nums[i++];
		}
		else {
			temp[index++] = nums[j++];
		}
	}
	while (i <= mid) {
		temp[index++] = nums[i++];
	}
	while (j <= right) {
		temp[index++] = nums[j++];
	}
	index = 0;
	for (int i = left; i <= right; i++) {
		nums[i] = temp[index++];
	}
	return;
}
void mergeSort(vector<int> &nums,int left,int right) {
	if (left < right) {
		int mid = (left + right) / 2;
		mergeSort(nums, left, mid);
		mergeSort(nums, mid + 1, right);
		mergeHelp(nums, left, mid, right);
	}
	return ;
}
//非递归,归并排序
void mergeSort2(vector<int> &nums) {
	int n = nums.size();
	//子数组的大小分别为1,2,4,,8...
	//刚开始合并的数组大小是1,接着是2,接着是4...
	for (int i = 1; i < n; i += i) {
		//进行数组的划分
		int left = 0;
		int mid = left + i - 1;
		int right = mid + i;
		//进行合并,对数组大小为i的数组进行两两合并
		while (right < n) {
			mergeHelp(nums, left, mid, right);
			left = right + 1;
			mid = left + i-1;
			right = mid + i;
		}
		//还有一些被遗漏的数组没有合并,别忘了
		//因为不可能每个字数组的大小都刚好为i
		if (left < n && mid < n) {
			mergeHelp(nums,left, mid, n - 1);
		}
	}
	return;
}

8、计数排序
性质:1、时间复杂度:O(n+k) 2、空间复杂度:O(k) 3、稳定排序 4、非原地排序
注:K表示临时数组的大小,下同

void countSort(vector<int> &nums) {
	if (nums.empty()) {
		return;
	}
	int n = nums.size();
	int maxSize = nums[0];
	for (int i = 1; i < n; i++) {
		if (nums[i]>maxSize) {
			maxSize = nums[i];
		}
	}
	vector<int> count(maxSize+1,0);
	for (int i = 0; i < n; i++) {
		count[nums[i]]++;
	}
	int index = 0;
	for (int i = 0; i < count.size(); i++) {
		if (count[i] > 0) {
			for (int j = 1; j <= count[i]; j++) {
				nums[index++] = i;
			}
		}
	}
	return;
}
void countSort2(vector<int> &nums){
	if (nums.empty()) {
		return;
	}
	int maxNum, minNum;
	maxNum = minNum = nums[0];
	for (int i = 1; i < nums.size(); i++) {
		if (nums[i] > maxNum) {
			maxNum = nums[i];
		}
		if (nums[i] < minNum) {
			minNum = nums[i];
		}
	}
	int size = maxNum - minNum + 1;
	vector<int> temp(size,0);
	for (int i = 0; i < nums.size(); i++) {
		temp[nums[i] - minNum]++;
	}
	int index = 0;
	for (int i = 0; i < temp.size(); i++) {
		for (int j = 0; j < temp[i]; j++) {
			nums[index++] = minNum + i;
		}
	}
	return;
}

9、桶排序
性质:1、时间复杂度:O(n+k) 2、空间复杂度:O(n+k) 3、稳定排序 4、非原地排序
注:k 表示桶的个数,下同

void BucketSort(vector<int> &nums) {
	if (nums.empty()) {
		return;
	}
	int n = nums.size();
	int maxNum = nums[0];
	int minNum = nums[0];
	for (int i = 1; i < n; i++) {
		maxNum = max(maxNum, nums[i]);
		minNum = min(minNum, nums[i]);
	}
	//和优化版本的计数排序一样,弄一个大小为min的偏移值
	int d = maxNum - minNum;
	//创建d/5+1个桶,第i个桶存放在5*i到5*i+5-1范围的数
	int bucketNum = d / 5 + 1;
	vector<vector<int>> bucketList(bucketNum);
	//遍历原数组,将每个元素放入桶中
	for (int i = 0; i < n; i++) {
		bucketList[(nums[i] - minNum)/d].push_back(nums[i] - minNum);
	}
	//对桶内的元素进行排序
	for (int i = 0; i < bucketNum; i++) {
		//这样是不可以的
		//vector<int> temp = bucketList[i];
		//sort(temp.begin(), temp.end());
		sort(bucketList[i].begin(), bucketList[i].end());
	}
	//把每个桶排序好的数据进行合并会中,放回原数组
	int k = 0;
	for (int i = 0; i < bucketNum; i++) {
		for (int item : bucketList[i]) {
			nums[k++] = item+minNum;
		}
	}
	return;
}

10、基数排序
性质:1、时间复杂度:O(kn) 2、空间复杂度:O(n+k) 3、稳定排序 4、非原地排序

void radioSort(vector<int> &nums) {
	if (nums.empty()) {
		return;
	}
	int n = nums.size();
	int maxNum = nums[0];
	for (int i = 1; i < n; i++) {
		maxNum = max(maxNum, nums[i]);
	}
	//计算最大值是几位数
	int bits = 1;
	while (maxNum / 10 > 0) {
		bits++;
		maxNum = maxNum / 10;
	}
	vector<vector<int>> bucketList(10);
	//进行每一趟的排序,从个位数开始拍
	for (int i = 1; i <= bits; i++) {
		for (int j = 0; j < n; j++) {
			//获取每个数最后第i位的数组
			int radio = ((nums[j] / (int)pow(10, i - 1))) % 10;
			//放进对应的桶里
			bucketList[radio].push_back(nums[j]);
		}
		//合并放回原数组
		int k = 0;
		for (int j = 0; j < 10; j++) {
			for (int item : bucketList[j]) {
				nums[k++] = item;
			}
			bucketList[j].clear();
		}
	}
	return;
}

int main_m() {
vector nums{ 22,3,3,3,4,5,6,67,7,6,43,2,4};
//QuickSort(nums, 0, (int)nums.size() - 1);

//QuickSort2(nums);
//selectSort(nums);
//mSort2(nums);
//shellSort(nums);
//mergeSort(nums, 0, (int)nums.size() - 1);
//mergeSort2(nums);
//countSort2(nums);
//BucketSort(nums);
radioSort(nums);
for (auto item : nums) {
	cout << item << " ";
}
cout << endl;
return 0;

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值