当前位置: 首页 > news >正文

数据结构(17)排序(下)

一、计数排序

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。操作步骤如下:①统计相同元素出现的次数  ②根据统计的结果将序列回收到原来的序列中

比如,现在有一个数组{6,1,2,9,4,2,4,1,4}。该数组中,元素1出现两次,元素2出现两次,元素4出现三次,元素6出现一次,元素9出现一次。我们这时就可以创建一个新数组,把原数组里的数据作为新数组的下标,原数组数据出现的次数作为新数组里下标对应的值,得到的新数组如下图所示:

这时我们定义一个变量 i 遍历 count 数组,如果count[i] 里的数据不为0,我们就循环向arr数组中插入count[i] 次,就可以得到排好序的数组,如下图所示:

思路很简单,但是新创建的count数组的大小如何确定呢?

在上面的示例中,我们似乎是找到了原数组中的最大值9,然后给count数组开辟了0~9共10个空间,那么是不是这样开辟空间大小就行呢?假如现在有一个数组{100,101,109,105,101,105},如果我们还像刚才那样开辟0~109共110个空间,那么count数组中下标为0~99这100个空间就严重浪费了。所以原数组中找最大值,再让最大值+1这个方法是错误的,可能会造成空间浪费。那该怎么做呢?我们还拿{100,101,109,105,101,105}这个数组为例,最大值max = 109,最小值min =100,100~109之间最多有10个数据,也就是说count数组所需的最大空间就是10,所以我们用max - min + 1就可以得到count数组的空间大小了。

注:计数排序在数据范围集中时效率很高,但是适用范围及场景有限!

代码如下:

//计数排序
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];}}// max - min + 1 确定count数组大小int range = max - min + 1;int* count = (int*)malloc(range * sizeof(int));if (count == NULL){perror("malloc fail");exit(1);}//初始化memset(count, 0, range * sizeof(int));for (int i = 0;i < n;i++){count[arr[i] - min]++;}//将count数组还原到原数组中,使其有序int index = 0;for (int i = 0;i < range;i++){while (count[i]--){arr[index++] = min + i;}}
}

计数排序时间复杂度为O(N + range)

稳定性:稳定

二、排序算法的稳定性分析

稳定性:假设在待排序的序列中,存在多个具有相同关键字的记录,经过排序,这些记录的相对次序保持不变。即在原序列中,r[i] == r[j],且r[i] 在 r[j] 之前,而排序后的序列,r[i] 仍在 r[j] 之前,则称这种排序算法是稳定的。

下面给出七种排序算法的时间复杂度和稳定性:

排序方法时间复杂度稳定性
冒泡排序O(n^2)稳定
直接选择排序O(n^2)不稳定
直接插入排序O(n^2)稳定
希尔排序O(nlogn)~O(n^2)不稳定
堆排序O(nlogn)不稳定
归并排序O(nlogn)稳定
快速排序O(nlogn)不稳定

三、快速排序 拓展

快速排序最核心的一点在于基准值的确定。关于如何找基准值,我们在上一章讲了三种方法。但是,当待排序数组中含有大量重复数据时,再使用之前的方法会让快速排序的时间复杂度接近于O(n^2)。因此,这里分享两种优化方法。

1、三路划分

三路划分,顾名思义,就是把数组划分成三个区间。把重复的相等的数据集中在中间,左边就是较小值,右边就是较大值。方法如下图所示:

根据上述的方法思路,我们来模拟实现一下该流程:

初始情况
第一步
第二步
第三步
第四步
cur > right,结束
单次循环结束状态

单次循环划分完毕之后,只需对左右两块区间递归即可。

void QuickSort(int* arr, int left, int right)
{if(left >= right){return;}int begin = left; int end = right;int key = arr[left];int cur = left + 1;while(cur <= right){if(arr[cur] < key){Swap(&arr[cur], &arr[left]);cur++;left++;}else if(arr[cur] > key){Swap(&arr[cur], &arr[right]);right--;}else{cur++;}}//[begin, left-1]  [left, right]  [right+1, end]QuickSort(arr, begin, left - 1);QuickSort(arr, right + 1, end);
}

2、自省排序(introsort)

introsort是introspective sort的缩写,顾名思义,该方法的思路就是进行自我的侦测与反省,快排递归深度太深(sgi STL中使用的是深度为2倍排序元素数量的对数值)那就说明在这种数据序列下,选key出现了问题,性能在快速退化,那么就不要再进行快排分割递归了,改换为堆排序进行排序。

了解即可,不做代码要求。

四、归并排序 拓展

1、非递归版本归并排序

//非递归版本归并排序
void MergeSortNonR(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");exit(1);}int gap = 1;while (gap < n){//根据gap划分组,两两一合并for (int i = 0;i < n;i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + gap + gap - 1;if (begin2 >= n){break;}if (end2 >= n){end2 = n - 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));}gap *= 2;}
}

2、文件归并排序

2.1 外排序

外排序(External sorting)是指能够处理极大量数据的排序算法。通常来说,外排序处理的数据不能能一次装入内存,只能放在读写较慢的外存储器(通常是硬盘)上。外排序通常采用的是一种“排序---归并”的策略。在排序阶段,先读入能放在内存中的数据量,将其排序输出到一个临时文件,依次进行,将待排序数据组织为多个有序的临时文件。然后在归并阶段将这些临时文件组合为一个大的有序文件,即排序结果。

跟外排序对应的就是内排序,我们之间讲的常见的排序都是内排序。它们的排序思想适应的是数据在内存中,支持随机访问。归并排序的思想是不需要随机访问数据,只需要依次按序列读取数据,所以归并排序既是一个内排序,也是一个外排序。

2.2 思路分析

2.3 参考代码

#include <stdio.h>
#include <stdlib.h>
#include <time.h>//创建N个随机数,写到文件中
void CreateNData()
{//造数据int n = 100000;srand(time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if(fin == NULL){perror("fopen fail");return;}for(int i = 0;i < n;i++){int x = rand() + i;fprintf(fin, "%d\n", x);}fclose(fin);
}int compare(const void* a, const void* b)
{return (*(int*)a - *(int*)b);
}//返回实际读到的数据个数,没有数据就返回0
int ReadNDataSortToFile(FILE* fout, int n, const char* file1)
{FILE* fin = fopen(file1, "w");if(fin == NULL){perror("fopen fail");return -1;}int x = 0;int* arr = (int*)malloc(sizeof(int) * n);if(arr == NULL){perror("malloc fail");return -1;}//读取n个数据,如果遇到文件结束,应该读j个int j = 0;for(int i = 0;i < n;i++){if(fscanf(fout, "%d", &x) == EOF){break;}arr[j++] = x;}if(j == 0){return 0;}//排序qsort(arr, j, sizeof(int), compare);//写回file1文件for(int i = 0;i < j;i++){fprintf(fin, "%d\n", arr[i]);}fclose(fin);return j;
}void MergeFile(const char* file1, const char* file2, const char* mfile)
{FILE* fout1 = fopen(file1, "r");if(fout1 == NULL){perror("fopen fail");return;}FILE* fout2 = fopen(file2, "r");if(fout2 == NULL){perror("fopen fail");return;}FILE* mfin = fopen(mfile, "r");if(mfin == NULL){perror("fopen fail");return;}int x1 = 0;int x2 = 0;int ret1 = fscanf(fout1, "%d", &x1);int ret2 = fscanf(fout2, "%d", &x2);while(ret1 != EOF && ret2 != EOF){if(x1 < x2){fprintf(mfin, "%d\n", x1);ret1 = fscanf(fout1, "%d", &x1);}else{fprintf(mfin, "%d\n", x2);ret2 = fscanf(fout2, "%d", &x2);}}while(ret1 != EOF){fprintf(mfin, "%d\n", x1);ret1 = fscanf(fout1, "%d", &x1);}while(ret2 != EOF){fprintf(mfin, "%d\n", x2);ret2 = fscanf(fout2, "%d", &x2);}
}int main()
{CreateNData();const char* file1 = "file1.txt";const char* file2 = "file2.txt";const char* mfile = "mfile.txt";FILE* fout = fopen("data.txt", "r");if(fout == NULL){perror("fopen fail");return;}ReadNDataSortToFile(fout, 100, file1);ReadNDataSortToFile(fout, 100, file2);while(1){MergeFile(file1, file2, mfile);//删除file1和file2remove(file1);remove(file2);//重命名mfile为file1rename(mfile, file1);//当再次读取数据,一个都读不到,说明已经没有数据了//已经归并完成,归并结果在file1if(ReadNDataSortToFile(fout, 100, file2) == 0){break;}}return 0;
}

http://www.dtcms.com/a/324891.html

相关文章:

  • 深度剖析 P vs NP 问题:计算领域的世纪谜题
  • Graham 算法求二维凸包
  • PG靶机 - Resourced
  • 【51单片机按键闪烁流水灯方向】2022-10-26
  • 【LeetCode】102 - 二叉树的层序遍历
  • MVC结构变种——第三章核心视图及控制器的整体逻辑
  • idea中使用maven造成每次都打印日志
  • matlab实现随机森林算法
  • [SUCTF 2019]Pythonginx
  • JS中typeof与instanceof的区别
  • 【精彩回顾·成都】成都 User Group×柴火创客空间:开源硬件驱动 AI 与云的创新实践!
  • JS 注释类型
  • ADK[3]历史对话信息保存机制与构建多轮对话机器人
  • scanpy单细胞转录组python教程(四):单样本数据分析之降维聚类及细胞注释
  • 【Canvas与戳记】黑底金Z字
  • 正确使用SQL Server中的Hint(10)— 常用Hint(2)
  • Spring WebSocket安全认证与权限控制解析
  • 研究揭示 Apple Intelligence 数据处理中可能存在隐私漏洞
  • 【redis初阶】------List 列表类型
  • 通过脚本修改MATLAB的数据字典
  • 【15】OpenCV C++实战篇——fitEllipse椭圆拟合、 Ellipse()画椭圆
  • 【人工智能99问】BERT的原理什么?(23/99)
  • Elasticsearch 保姆级入门篇
  • SpringBoot查询方式全解析
  • 在Mac上搭建本地AI工作流:Dify与DeepSeek的完美结合
  • 数字图像处理2——图像增强
  • AI(1)-神经网络(正向传播与反向传播)
  • 【RL第七篇】PPO训练策略,如何使PPO训练稳定?
  • unity中如何让原人物动画兼容新人物的动画
  • 异步问题的概念和消除问题技巧