文章目录
欢迎大家来到我的博客,很高兴又与大家见面,给生活加点imprtus,开启今天的编程之路!!
今天我们来给排序收个尾,了解基础哈希算法,感受递归魅力!!
欢迎点赞关注!!
排序
归并排序
归并排序算法思想:
归并排序(MERGE-SORT)是建立在归并操作上的⼀种有效的排序算法,该算法是采用分治法 (Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列 ;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成⼀个有序表 ,称为**⼆路归并**。
递归版本
归并排序核心步骤。我们先来画图演示一下:
我们可以得出:
当数组只剩一个元素时 ,此时左端点等于右端点 ,如果我们设置left和right指针的话,就等价于left==right ,此时数组一定有序。
而且,因为函数递归的时候会不断创建函数栈帧,当递归结束之后,函数中会保存着对应的区间范围,我们利用这个特性再来进行合并
思路:确定中间值,随后分为两个区间 ,直到区间只剩一个元素 ,可以认为每一数组是有序的 ,分解之后来合并两个序列到数组tmp中,随后再讲将tmp赋值给原数组
代码实现

参数初始化left=0,right=n-1,tmp就是新创建的数组,最后要赋值给原数组
递归部分写完了,我们再来看合并部分:
这里我们可以来画图模拟一下思路:
为什么这里需要设置begin1,begin2,end1,en2?
保留left指针的原本的属性,为了之后赋值做准备
来看代码:
c
//合并两个序列:[left,mid] [mid+1,right]
int begin1 = left, end1 = mid;//这样可以保证left保存的数值不变
int begin2 = mid + 1, end2 = right;
int index = begin1;
while (begin1 <= end1 && begin2 <= end2)
{
if (arr[begin1] < arr[begin2])//更小的数据入数组,同时对应的位置指针向后遍历
{
tmp[index++] = arr[begin1++];
}
else {
tmp[index++] = arr[begin2++];
}
}
//左序列数据没有全部放到tmp数组中
//右序列数据没有全部放到tmp数组中
while (begin1 <= end1)//因为我需要循环一直放入,所以我需要使用循环语句而不是判断语句
{
tmp[index++] = arr[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = arr[begin2++];
}
//我们成功将正确顺序的数组给到tmp数组之后,我们将tmp数组的顺序赋值给原数组
for (int i = left;i <= right;i++)
{
arr[i] = tmp[i];
}
}
赋值的时候为什么这里int i=left?
当函数中递归部分结束的时候,此时left和right的信息这一个元素的函数栈帧中仍然保留的,如果让i=0,会造成0这个位置的数据一直变动
这种方法存在递归,而且类似于二叉树,会一直二分元素个数
含递归算法的时间复杂度:递归次数*每次递归的时间复杂度
时间复杂度:O(n*logn)
空间复杂度:O(n+Range)因为我不清楚n于Range谁更大
非递归版本
在非递归版本中,我们直接对数组进行操作,因为递归版本递归结束后存储着left和right信息,所以能够找到对应的区间,非递归时,我们需要设置gap变量来寻找left和right信息。
我们看一下下方图解:
我们先来说明思路:
创建新的数组tmp,是为了来放置排序好的数组元素 ->以每一次的gap来合并两个有序数组 ->导入到原数组
c
void MergeSortNonR(int* arr, int n)
{
//申请相同数组大小,按gap分组并一直比较并一直会赋值
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("mmalloc failed!!");
exit(1);
}
//申请成功
int gap = 1;//一开始只有一个元素为一组
while (gap<n)//当gap==n时,此时只有一组,就没有必要继续比较了,直接退出循环
{
//gap为固定值时,按组对所有数据进行操作
//根据gap分组,两两一合并
for (int i = 0;i < n;i += 2 * gap)//对i变量的操作有点没懂
//对变量i进行操作,因为我每次我都要跨过一组两个区间,例如当gap=1时,要一次性跨过2个元素,就是2*gap个元素
{
int begin1 = i, end1 = i + gap - 1;//gap代表每组的元素个数,i+这组包含元素个数,随后再利用-使得右区间对得上
//第二个范围的左区间就是end1+1
int begin2 = i + gap, end2 = i + gap + gap - 1;
int index= begin1;
//两个有序序列进行合并
while (begin1 <= end1 && begin2 <= end2)
{
if (arr[begin1] < arr[begin2])
{
tmp[index++] = arr[begin1++];
}
else {
tmp[index++] = arr[begin2++];
}
}
while (begin1 <= end1)
{
tmp[index++] = arr[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = arr[begin2++];
}
//导入到原数组
memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));
//例如,做序列10[0,0],右序列6[1,1],则此时需要拷贝两个数据,并且此时i=0
}
gap *= 2;//合并之后,每组元素个数呈2倍增长
}
}
细节
1:对变量i进行操作,因为我每次我都要跨过一组两个区间,例如当gap=1时,要一次性跨过2个元素,就是2*gap个元素
2:做序列10[0,0],右序列6[1,1],则此时需要拷贝两个数据,并且此时i=0,所以拷贝end2-i+1个元素
3:我是要对对应组进行排序,所以需要begin1的位置开始放入数据,index初始化为begin1
代码实现
这里我们需要说明一下细节:
随着gap的增加,有没有可能没有右序列或者有序列不全的
我们来看下方这张图:
左序列存在,右序列不存在 或者左序列未满gap个元素
我们直接跳过这一次循环
再来看一张图:
左右序列都存在,但是右序列未满
直接将end2重置为最后一个元素的下标
最后应该在初始化begin1,begin2,end1,end2之后加上这句代码:
c
if (begin2 >= n)//没有右序列,此时只有左边的组,例如:左[2,3],右没有,或者左边的数据个数没有满
{
break;//此时就直接进入下一个gap了
}
if (end2 >= n)//右序列不满gap个数据
{
end2 = n - 1;//左序列和右序列都有个数,但是,右序列不全,左序列完全
//此时n-1就可以直接走到最后一个元素的下标
}
非比较排序--计数排序
2.6.1 计数排序:
计数排序⼜称为鸽巢原理,是对哈希直接定址法 的变形应用。操作步骤:
1:统计相同元素出现次数
2:根据统计的结果将序列回收到原来的序列中
我们来画一个图说明一下:
那么我们应该如何确定这个count数组的大小呢?
首先最简单的,也是比较容易想到的,就是直接最大值+1 ,因为我需要保证每个元素在count数组中都有对应的下标
不妨我们再来举例:
当我们数值过大时,就会造成大量的空间浪费,如果是这种情况,我们应该怎么办呢?
但是这样也不是万能的,如果说min和max数值相差过大,仍然会造成大量空间浪费 。
所以我们可以得出
计数排序的特性:
计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
代码实现
思路:找出数组的最值,之后创建新的count计数数组,最后计数,再遍历count数组求得原数组的顺序
c
void CountSort(int* arr, int n)
{
//寻找最值
int min = arr[0], max = arr[0];
for (int i = 1;i < n;i++)//因为0已经被设置为了两个最值,所以这里我们使用1开始遍历
{
if (arr[i] < min)
{
min = arr[i];
}
if (arr[i] > max)
{
max = arr[i];
}
}
//根据最大值来创建数组空间
int Range = max - min + 1;
int* count = (int*)malloc(sizeof(int) * Range);
if (count == NULL)
{
perror("malloc falied!!");
exit(1);//退出码为1
}
//初始化count数组
memset(count, 0, sizeof(int) * Range);//memset()函数用于将一段内存区域设置为指定的值
//直接用calloc也行,创建的时候就直接初始化为0了
//int* count = (int*)calloc(Range, sizeof(int));
//if (count == NULL)
//{
// preeor("calloc falied!!");
// exit(1);
//}
//计数
for (int i = 0;i < n;i++)
{
count[arr[i] - min]++;
}
//直接根据哈希表来打印数组,同时赋值给原数组
int index = 0;
for (int i = 0;i < Range;i++)
{
//数据出现的次数count[i]
//下标---------源数据 i+min
while (count[i]--)
{
arr[index++] = i + min;
}
}
}
结语
感谢大家阅读我的博客,不足之处欢迎指正交流!!
劝君莫惜金缕衣,劝君惜取少年时 !!加油!!