
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即显示每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。

(递归实现)
void _Merge_sort(int* arr, int begin, int end, int* tmp)
{
	if (begin == end)
	{
		return;
	}//从单个元素开始递归
	int mid = (begin + end) / 2;//划分为两个空间,等待回归
	_Merge_sort(arr, begin, mid, tmp);
	_Merge_sort(arr, mid + 1, end, tmp);
	//开始归并
	int begin1 = begin, end1 = mid;
	int begin2 = end1 + 1, end2 = end;
	int i = begin;//i来记录此次归并开始的位置
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] <= arr[begin2])
		{
			tmp[i++] = arr[begin1++];
		}
		else
		{
			tmp[i++] = arr[begin2++];
		}
	}
	//当两部分有一方出现end大于begin时,退出循环,此时另外一方可能出现数据没完全拷贝回去的情况
	while (begin1 <= end1)
	{
		tmp[i++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = arr[begin2++];
	}
	//此时tmp中的数已经排好顺序,需要返回到原数组
	memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void Merge_sort(int* arr, int len)//由于我们打算用递归来写,所以最好将递归部分包装出去成为一个接口
{
	//开辟一个同等大小的数组
	int* tmp = (int*)malloc(sizeof(int) * len);
	if (tmp == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	_Merge_sort(arr, 0, len - 1, tmp);
	free(tmp);
	tmp = NULL;
}(迭代实现)
void Merge_sort(int* arr, int len)//非递归思想
{
	int* tmp = (int*)malloc(sizeof(int) * len);
	if (tmp == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	int gap = 1;//gap代表是当前所分两组,每组的元数个数
	while (gap < len)
	{
		for (int i = 0; i < len; i += 2 * gap)
		{
			int begin1 = i, end1 = begin1 + gap - 1;
			int begin2 = begin1 + gap, end2 = begin2 + gap - 1;
			int j = i;
			if (end1 >= len || begin2 >= len)
			{
				break;//判断,小心此次循环出现数据越界
			}
			//还有一种end2越界但是begin2没有越界
			if (end2 >= len)
			{
				end2 = len - 1;
			}
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (arr[begin1] <= arr[begin2])
				{
					tmp[j++] = arr[begin1++];
				}
				else
				{
					tmp[j++] = arr[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = arr[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = arr[begin2++];
			}
			memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
堆排序的平均时间复杂度为 Ο(nlogn)。


void AdjustDown(int* arr, int len, int parent)
{
	int child = parent * 2 + 1;
	while (child < len)
	{
		if (child + 1 < len && arr[child] < arr[child + 1])\
		{
			child += 1;
		}
		if (arr[parent] < arr[child])
		{
			Swap(arr[parent], arr[child]);
			parent = child;
			child = child * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void Heap_sort(int* arr, int len)
{
	int size = len;
	while (size > 0)
	{
		for (int i = (size - 1 - 1) / 2; i >= 0; i--)
		{
			AdjustDown(arr, size, i);
		}//挑选最大的到堆顶
		Swap(arr[0], arr[--size]);
	}
}快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好。
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割为两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
快速排序递归实现的主框架与二叉树前序遍历规则非常像,我们在写递归框架时可想想二叉树前序遍历的规则即可快速写出来,后序只需要分析如何按照基准值来对区间中数据进行划分的方式即可。
(hoare版本)

(前后指针法)

(hoare版本)
void Swap(int& a, int& b)
{
	int c = a;
	a = b;
	b = c;
}
//快速排序
//hoare版本
void Quick_sort(int* arr, int left, int right)
{
	if (left > right)
	{
		return ;
	}
	int key = left;
	int begin = left, end = right;
	while (left < right)
	{
		while (left < right && arr[right] >= arr[key])//在循环过程中,容易出现分循环一直没找到小于的值,right一直减,让right<小于left的情况
			//所以安排循环条件内加入left<right。a[right]>=a[left]的等于是怕出现相同的值,导致循环断掉,出现卡死循环的情况
		{
			right--;
		}//直到找到一个值小于a[key]
		while (left<right && arr[left]>arr[key])
		{
			left++;
		}
		Swap(arr[right], arr[left]);
	}
	//退出循环时right与left相遇
	Swap(arr[key], arr[left]);
	key = left;//交换之后更新key的值充当分割点坐标进行下面的递归
	Quick_sort(arr, begin, key - 1);
	Quick_sort(arr, key, end);
}(双指针)
//双指针法
void Quick_sort(int* arr, int left, int right)
{
	if (right <= left)
	{
		return;
	}
	int prew = left, cur = left + 1;
	int keyi = left;
	while (cur <= right)
	{
		if (arr[cur] < arr[keyi] && ++prew != cur)
		{
			Swap(arr[cur], arr[prew]);
		}
		cur++;
	}
	Swap(arr[keyi], arr[prew]);
	keyi = prew;
	Quick_sort(arr, left, keyi - 1);
	Quick_sort(arr, keyi + 1, right);
}计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
1. 计数排序的特征
当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。
由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。
通俗地理解,例如有 10 个年龄不同的人,统计出有 8 个人的年龄比 A 小,那 A 的年龄就排在第 9 位,用这个方法可以得到其他每个人的位置,也就排好了序。当然,年龄有重复时需要特殊处理(保证稳定性),这就是为什么最后要反向填充目标数组,以及将每个数字的统计减去 1 的原因。

void Count_sort(int* arr, int len)
{
	int min = arr[0], max = arr[0];
	for (int i = 1; i < len; i++)
	{
		if (min > arr[i])
		{
			min = arr[i];
		}
		if (max < arr[i])
		{
			max = arr[i];
		}
	}
	int range = max - min + 1;
	int* tmp = (int*)calloc(range, sizeof(int));
	if (tmp == NULL)
	{
		perror("calloc fail!");
		exit(1);
	}
	for (int i = 0; i < len; i++)
	{
		tmp[arr[i] - min]++;
	}
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (tmp[i]--)
		{
			arr[j++] = min + i;
		}
	}
}