归并排序
归并排序(MERGE-SORT)是建⽴在归并操作上的⼀种有效的排序算法,该算法是采⽤分治法(Divide and Conquer)的⼀个⾮常典型的应⽤。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成⼀个有序表,称为⼆路归并。
算法分析
分解: 利用mid=(left+right)/2求出中间值,利用左侧[left,mid],右侧[mid+1,right]的划分,分解的结束条件是数组的长度为1时。
**递归合并:**数组递归的结束条件时当left>=right,类似与二叉树的尾插,然后对左右两侧的数组进行比大小后合并,依次上返回。
算法特性
‧ 时间复杂度为 O(n log n):划分产生高度为 log n 的递归树,每层合并的总操作数量 为 n ,因此总体时间复杂度为 O(n log n) 。
‧ 空间复杂度为 O(n):递归深度为 log n,使用 O(log n) 大小的栈帧空间。合并操作需 要借助辅助数组实现,使用 O(n) 大小的额外空间。
‧ 稳定排序:在合并过程中,相等元素的次序保持不变。
源代码
cpp
//归并排序
void _MergeSort(int* arr, int left, int right, int* tem)
{
//递归
if (left >= right)
{
return;
}
int mid = (left + right) / 2;
_MergeSort(arr, left, mid, tem);
_MergeSort(arr, mid + 1, right, tem);
//合并
int bagan1 = left, bagan2 = mid + 1;
int end1 = mid, end2 = right;
int index = bagan1;
while (bagan1 <= end1 && bagan2 <= end2)
{
if (arr[bagan1] < arr[bagan2])
{
tem[index++] = arr[bagan1++];
}
else
{
tem[index++] = arr[bagan2++];
}
}
//越界情况
//2越界的情况
while (bagan1<=end1)
{
tem[index++] = arr[bagan1++];
}
//1越界的情况
while (bagan2 <= end2)
{
tem[index++] = arr[bagan2++];
}
for (int i = left; i <=right; i++)
{
arr[i] = tem[i];
}
}
void MergeSort(int* arr, int n)
{
//开辟空间
int* tem = (int*)malloc(sizeof(int) * n);
_MergeSort(arr, 0, n-1, tem);
free(tem);
}
计数排序
算法分析
计数排序⼜称为鸽巢原理,是对哈希直接定址法的变形应⽤。操作步骤:
1)统计相同元素出现次数
2)根据统计的结果将序列回收到原来的序列中
如何解决空间浪费的情况:
因为,我记数排序要先开辟空间,把值放进去,但是当最大值较大,数据所在的区间又较为密集的时候,就会出现上述状况,造成空间的浪费。因此我们利用max-min算出所要开辟的空间的范围,从而避免空间的浪费。
如何进行存储负数:
我们知道负数在数组中是无法表示的,这时我们可能会想到,取绝对值,但是如果出现一个负数的绝对值后的值正好和原数组中某一个数一样是不是就会出现错误。解决方案:让最小的负数减去本身即可。
时间复杂度,空间复杂度
空间复杂度:O(range)
时间复杂度:O(N +range),但是有同学会想难度不是时间复杂度:O(N*range)?
这里面内层循环就是n个数据循环n次,外层是一次遍历所以这里的时间复杂度是n+range,而不是range*n;
计数排序的特性
- 计数排序在数据范围集中时,效率很⾼,但是适⽤范围及场景有限。
- 只使用整数不适应小数。
- 稳定性高
源代码
cpp
//计数排序
void CountSort(int* arr, int n)
{
//先确定范围
int min = arr[0], max = arr[0];
for (int i = 1; i < n; i++)
{
if (arr[i] < min)
{
min = arr[i];
}
if (arr[i] > max)
{
max = arr[i];
}
}
int range = max - min + 1;
//开辟空间
int* tem = (int*)malloc(sizeof(int) * range);
if (tem == NULL)
{
perror("malloc fail!!!");
exit(1);
}
//初始化,利用memset函数
memset(tem, 0, sizeof(int) * range);
//
for (int i = 0; i < n; i++)
{
tem[arr[i] - min]++;
}
int index = 0;
for (int i = 0; i < range; i++)
{
//解决数据的重复出现,只要不为零就会输出,直到变为零位置
while (tem[i]--)
{
arr[index++] = i + min;
}
}
}
总结
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的 相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,⽽在排序后的序列中,r[i]仍在r[j]之 前,则称这种排序算法是稳定的;否则称为不稳定的。
数据结构结构初阶的排序基本上总结完毕了,这里我学了冒泡,插入,选择,快排,希尔,堆,归并,计数排序,这几种排序在时间复杂度,空间复杂度以及稳定性上各有各的优点。下面我们用一张图进行总结。