算法实现-排序相关

#pragma once
#include <vector>
#include <string>
#include <iostream>

using namespace std;

namespace Sort
{
	// 基本思想:通过一趟排序将数分割为左,右两部分,并选择出基数,使得左边的都得基数小,右边的都比基数大,并依此递归操作。
	// 1. 快速排序是分治思想的又一典型代表,是应用最广的排序算法。
	// 2. 分治思想就是把原问题的解分解为两个或多个子问题解,求解出子问题的解之后再构造出原
	// 问题的解。
	// 3. 在快速排序算法中,它的思想是把一个待排序的数组分成前半部分和后半部分,并且要求
	// 前半部分的值都大于等于或都小于等于后半部分的解, 当前半部分与后半部分都变成有序(通
	// 过递归调用快速排序来实现)后,我们就不需要合并两个子问题的解就已经得到了原问题的解。
	// 这也是为什么要求前半部分都大于等于或都小于等于后半部分的原因。
	// 4.所以呢,快速排序的核心在于如何把一个待排序的数组分成两部分!
	//
	// 核心点:
	// 1. 如何把待排序的数组划分为符合要求的两部分!
	// 2. 期望的时间复杂度为O(NlogN), 最坏的时间复杂度为O(N*N)
	// 3. 快速排序为原址排序,不需要额外的内存空间.
	// 4. 快速排序不是稳定排序, 在交换过程中会破坏稳定性。
	// 快速排序算法
	int GetIndex(vector<int>& arr, int left, int right);
	void QuickSort(vector<int>& arr, int left, int right)
	{
		if (left < right)
		{
			// 获取基准
			int index = GetIndex(arr, left, right);
			QuickSort(arr, left, index - 1); // 递归排序基准右边
			QuickSort(arr, index + 1, right); // 递归排序基准右边
		}
	}

	// 快速排序获取基准
	int GetIndex(vector<int>& arr, int left, int right)
	{
		int pivot = arr[left];
		while (left < right)
		{
			while (left < right && arr[right] > pivot) // 先找基准左边较小的一个数
				right--;
			arr[left] = arr[right]; // 先找基准左边较小的一个数,并和基准位置交换

			while (left < right && arr[left] <= pivot) // 先找基准右边较大的一个数
				left++;
			arr[right] = arr[left]; // 先找基准右边较大的一个数,并和上一次空闲的基准右边的较大值位置交换
		}
		arr[left] = pivot; // 最后将基准放在合适的位置

		return left;
	}


	/*归并排序:
	算法思想:分而治之(分三步骤)
	第一, 分解: 把待排序的 n 个元素的序列分解成两个子序列, 每个子序列包括 n/2 个元素.
	第二, 解决: 对每个子序列分别调用归并排序MergeSort, 进行递归操作
	第三, 合并: 合并两个排好序的子序列,生成排序结果.
	时间复杂度分析:
	第一步:分解,时间复杂度为θ(1)
	第二步:解决,对每个子序列进行递归操作。时间复杂度为:2T(n/2)
	第三步:合并,类似于插入操作,子序列有n个元素,最多操作n次,时间复杂度为θ(n)
	总的时间复杂度为:T(n)=2T(n/2)+θ(n).由递归树得时间复杂度为O(nlog n).
	————————————————*/
	// 归并排序
	void Merge(vector<int>& arr, int left, int mid, int right);
	void MergeSort(vector<int>& arr, int left, int right)
	{
		if (left < right)
		{
			int mid = (right + left) / 2;
			MergeSort(arr, left, mid);		// 对中间位置右边进行递归归并
			MergeSort(arr, mid + 1, right); // 对中间位置左边进行递归归并
			Merge(arr, left, mid, right);	// 合并左右两个已经排序的序列
		}
	}

	// 合并两个子序列,mid是两个子序列中间边界,mid索引归左边
	// 合并子序列需要借助最大right-left+1长度的辅助空间
	void Merge(vector<int>& arr, int left, int mid, int right)
	{
		int i = 0; // 辅助空间的索引
		vector<int> vt_temp(right - left + 1, 0);

		// 比较两个子序列,将较小值一一拷贝到辅助空间,直到某一个序列先被比较完
		int l = left, r = mid + 1;
		while (l <= mid && r <= right)
		{
			if (arr[l] > arr[r])
				vt_temp[i++] = arr[r++];
			else
				vt_temp[i++] = arr[l++];
		}

		// 假设左边的序列没比完,将剩下的拷贝到辅助空间
		while (l <= mid)
		{
			vt_temp[i++] = arr[l++];
		}

		// 假右边的序列没比完,将剩下的拷贝到辅助空间
		while (r <= right)
		{
			vt_temp[i++] = arr[r++];
		}

		// 将辅助空间的序列拷贝到arr
		i = 0;
		while (left <= right)
		{
			arr[left++] = vt_temp[i++];
		}
	}
	

	//交换两个元素的值
	void Swap(int&i, int& j)
	{
		int temp = i;
		i = j;
		j = temp;
	}

	/*
	堆排序是指利用堆这种数据结构所设计的一种选择排序算法。
	堆是一种近似完全二叉树的结构(通常堆是通过一维数组来实现的),并满足性质:以最大堆(也叫大根堆、大顶堆)为例,其中父结点的值总是大于它的孩子节点。
	实际的物理结构是数组。堆排序可以用到上一次的排序结果,所以不像其他一般的排序方法一样,每次都要进行n-1次的比较,复杂度为O(nlogn)。
	堆排序的时间复杂度O(N*logN),额外空间复杂度O(1),是一个不稳定性的排序。

	在第i个元素的索引为i的情形中:
	性质一:父结点索引为(i-1)/2,整型时会省略小数部分
	性质一:左孩子索引为 (2*i+1);
	性质二:右孩子索引 (2*i+2);

	堆排序思想:
	1)利用给定数组创建一个堆,输出堆顶元素
	2)以最后一个元素代替堆顶,调整成堆,输出堆顶元素
	3)把堆的尺寸缩小1
	4)重复步骤2,直到堆的尺寸为1
	*/
	// 堆排序的核心是建堆,传入参数为数组,根节点位置,数组长度
	void HeapBuild(vector<int>& vect, int root, int len)
	{
		int lChild = 2 * root + 1; //根节点的左子结点下标
		if (lChild < len) //左子结点下标不能超出数组的长度
		{
			int maxIndex = lChild; // 保存左右节点中较大值的下标
			int rChild = lChild + 1; // 根节点的右子结点下标
			if (rChild < len) //右子结点下标不能超出数组的长度(如果有的话)
			{
				if (vect[lChild] < vect[rChild])
				{
					maxIndex = rChild;
				}
			}

			if (vect[root] < vect[maxIndex]) // 父节点的值小于子节点中较大的,需要交互父节点与该节点的值
			{
				// 交换父结点和比父结点大的最大子节点
				Swap(vect[root], vect[maxIndex]);

				//从此次最大子节点的那个位置开始递归建堆
				HeapBuild(vect, maxIndex, len);
			}
		}
	}

	// 堆排序-大根堆
	void HeapSort(vector<int>& vect)
	{
		int len = vect.size();
		if (len < 2)
			return;

		// 1. 无序数组建堆
		// 上升调整堆结构
		// 从(n/2-1) --> 0逐次遍历。遍历之后,得到的数组实际上是一个(最大)二叉堆。
		// len-1 为数组最后一个元素下标,((len-1)-1)/2满足最后一个非叶子节点的索引,从最后一个非叶子节点的父结点开始建堆
		for (int i = ((len - 1) - 1) / 2; i >= 0; --i)
		{
			HeapBuild(vect, i, len);
		}

		for (int j = len - 1; j >= 0; --j) //j表示数组此时的长度,因为len长度已经建过了,从len-1开始
		{
			Swap(vect[0], vect[j]);		// 2.交换首尾元素,将最大值交换到数组的最后位置保存
			HeapBuild(vect, 0, j);		// 3.尺寸缩小,去除最后位置的元素重新建堆,此处j表示数组的长度,第一次调整时,最后一个位置下标变为len-2
		}
	}

	// 打印
	void PritSortStr(const vector<int>& vect, string fun)
	{
		printf("%s: ", fun.c_str());
		for (auto it : vect)
		{
			printf("%d, ", it);
		}
		printf("\n");
	}
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值