chapter3-排序和查找
1.排序
1.1基础排序
排序就是把一组无序的数据变成有序的过程。对于机试而言,直接使用C++封装好的sort
函数就可以了。sort函数内部采用快速排序实现,因此非常高效。使用sort函数,需要引用#include <algorithm>
头文件,sort(first_address, last_address, compare)
有三个参数,first和last待排序序列的起始地址和结束地址;compare为比较方式。如果不规定第三个参数,则默认是从小到大升序方式。
注意,1、sort函数的地址是左闭右开区间的。2、如果要对给定的数组进行降序排列或以自定义的方式排列,那么第三个参数会起作用。可以通过编写比较函数的方式来实现想要的自定义排序方式。
排序,既包括对内部已经定义的基本数据类型的排序,如整型、字符型等,也包括对自定义数据类型的排序,如结构体或者类。所以要注意,整数是有大小关系的,而结构体变量本身是没有大小关系的,sort函数的默认排序方式无法满足要求,必须依据题面描述写一个自定义的函数才能完成上述要求。
考生在遇到新的排序规则时,只需记住如下这条黄金法则:当比较函数的返回值为true时,表示的是比较函数的第一个参数将会排在第二个参数前面。
所有的比较,最终还是要转换成为对于大小的比较,奇偶性就是考察模2之后值的大小比较,一切比较都是基于两个数之间如何定义它们的大小关系,找出内部的大小关系,设计出合格的Compare比较函数。
1.1整数奇偶排序
奇偶排序_代码实现
//排序-习题3.2 整数奇偶排序 2024-02-09 #include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int MAXN=10+2; int arr[MAXN]; //自定义比较函数,确定谁在前谁在后 bool compare(int a, int b) { if(a%2 == 1 && b%2 == 1) { return b < a; }else if (a%2 == 0 && b%2 == 0) { return a < b; }else if (a%2 == 1 && b%2 == 0) { return true; }else{//情况4:a为偶数,b为奇数 return false; } } int main() { while(scanf("%d", &arr[0]) != EOF) { for(int i=1; i<10; i++) { scanf("%d", &arr[i]); } sort(arr, arr + 10, compare);//注意:sort函数的地址参数是左闭右开的 for(int i=0; i<10; i++) { printf("%d", arr[i]); if(i != 9) printf(" "); } printf("\n"); } return 0; }
1.2扩展排序——如何根据一些排序算法的特性去求解特殊的问题。
1.2.1问题1:线性排序
即在线性O(n)时间内实现对一个序列的排序,这就需要用到计数排序的特性。
我们知道,基于比较实现的排序时间复杂度下届是O(nlogn),那么要实现线性时间排序,就不能基于比较,而是用计数排序。注意使用计数排序的前提是,给定序列的所有数据是有一个范围的,开一个和数据范围一样大的辅助数组进行计数。
计数排序
//计数排序-杭州电子科技大学1425 //2024-02-14 #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1e6+10; const int RANGE = 5e5; int arr[MAXN]={0};//有多少个数据的范围 int tmp[MAXN]={0};//数据本身的范围。辅助数组,用于计数 int main() { int n,m; while(scanf("%d%d", &n, &m) != EOF) { memset(tmp, 0, sizeof(tmp)); //因为有多组测试数据,每次要清空辅助数组 for(int i=0; i<n; i++) { scanf("%d", &arr[i]); tmp[arr[i]+RANGE]++; //偏移5e5计数 } int index = 0; for(int i=0; i < MAXN; i++) { while(tmp[i]--)//把计数数组对应下标i存回原数组 { arr[index++] = i - RANGE; } } for(int i=n-1; i >= n-m; i--) //从后往前输出前m大的数 { if(i == n - m)//输出格式控制,不错 { printf("%d\n", arr[i]); } else{ printf("%d ", arr[i]); } } } return 0; }
memset( the_array, '\0', sizeof(the_array) );
这是将一个数组的所有分量设置成零的很便捷的方法。使用memset要引用头文件#include <string.h>
(C语言)或者#include <cstring>
(C++中使用)。
2、辅助数组计数完之后,为了后续的使用,也为了输出的便捷性,可以把辅助数组下标(即数据)再存回原数组。
3、杭电OJ很奇怪,我用G++编译运行会报超时错误,同样的代码在C++编译运行就通过了,不过注意一下初始化一个1e6次的数组是占用一定时间的,所以对于大的数组可以不初始化。
1.2.2问题2:求逆序数对
即如何快速求出一个序列的逆序数,这个问题需要用到归并排序的特点。
对一个无序序列归并成有序序列,如图所示,先拆分直至只剩一个元素,这时仅含单个元素的子序列就是有序的,然后合并,不断地把两个有序子序列合成一个更大的有序子序列,最终完成一整个序列的排序。在这个过程中,每一趟拆分或者合并都是线性时间O(n)完成的,一共2logn趟。算法时间复杂度最好、最坏都是O(nlogn)。
合并的过程如图。当后一个有序子序列的元素比前一个子序列的某个元素小时,就产生了逆序数,值为middle + 1 - i
。
归并排序+计算逆序数对
//归并排序的同时附带求逆序数对 2024-02-14 #include <iostream> #include <cstdio> using namespace std; const int MAXN = 1000 + 10; int arr[MAXN]; int temp[MAXN]; int number; //用于计数,记录逆序数个数 //把两个有序子序列,合并成为一个更大的有序序列 void Combine(int left, int middle, int right) { int i = left,j = middle + 1; int k = left; //temp数组的索引 while(i <= middle && j <= right) { if(arr[i] <= arr[j]){ temp[k++] = arr[i++]; } else{ number += middle + 1 - i; temp[k++] = arr[j++]; } } while(i <= middle) { temp[k++] = arr[i++]; } while(j <= right) { temp[k++] = arr[j++]; } //最后把合并完的有序序列存回原始数组arr for(k= left; k <= right; k++) { arr[k] = temp[k]; } } void MergeSort(int left, int right) { //注意:不能写while(left < right),这样递归没有出口,永远不会跳出函数栈,陷入死循环!!! if(left < right)//拆分直至单个元素必为有序子序列 { // middle = (left + right) / 2; //这种写法存在溢出问题 int middle = left + (right - left) / 2; MergeSort(left, middle); MergeSort(middle+1, right); Combine(left, middle, right); } } int main() { int n; scanf("%d", &n); number = 0; for(int i=0; i<n; i++) { scanf("%d", &arr[i]); } MergeSort(0,n-1); for(int i=0; i<n; i++)//输出排序后的结果 { if(i == n-1){ printf("%d\n", arr[i]); } else{ printf("%d ", arr[i]); } } //实现在归并排序中附带计算逆序数的大小 printf("该序列的逆序数为:%d\n", number); return 0; }
1.2.3问题3:第K大数
即如何快速求出一个序列中第K大的数,需要用到快速排序的特性。
使得能够在线性O(n)的时间内获得结果。而朴素的想法是先通过排序,将无序序列变为有序序列,再定位数组arr[n-k]
即为第K大的数,时间复杂度为O(nlogn)。
快速排序和归并排序一样同样通过递归实现。通过取枢轴元素,将整个无序序列一分为二,枢轴左边的元素都比它小,枢轴右边的元素都比它大;在左边的子序列中重复操作,即再取一个子序列的枢轴元素,一分为二,枢轴左边的元素都比它小,枢轴右边的元素都比它大;右边子序列同样;子序列的子序列只要还有1个以上的元素,就继续上述操作,直至序列中只剩一个元素,此时必定有序,也就是到了递归的出口条件left==right
。
依据上图,写出快排的实现。
快速排序
//快速排序实现 2024-02-14 #include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> using namespace std; const int MAXN = 1000 + 10; int arr[MAXN]; //确定序列的枢轴元素,并遍历使枢轴左边的元素都比它小,右边都比它大 int Partition(int left, int right) { int pos = left + rand() % (right - left + 1);//得到[left, right]区间内的一个随机数 swap(arr[left], arr[pos]); //保证快排性能 int pivot = arr[left];//枢轴 while(left < right) { while(left < right && arr[left] < arr[right]) { right--; } swap(arr[left], arr[right]); while(left < right && arr[left] < arr[right]) { left++; } swap(arr[left], arr[right]); } return left; } void QuickSort(int left, int right) { if(left < right) { int position = Partition(left, right); QuickSort(left, position -1); QuickSort(position + 1, right); } } int main() { int n; scanf("%d", &n); for(int i=0; i<n; i++) { scanf("%d", &arr[i]); } QuickSort(0, n-1); for(int i=0; i<n; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
进一步,我们如何利用快排的特性,在O(n)线性时间内找到无序序列中第K大的数呢?设无序序列总共有n个元素,第K大的数在有序序列中的数组下标就应该是n-k,而在每一轮的快排中,我们随机指定的枢轴元素都能找到它的最终位置,如图所示。故假如该枢轴元素刚好下标就是n-k,那么我们就找到了第K大的数,算法结束;假如此轮枢轴元素下标比n-k小,那么我们也能判断出第K大的数必定在枢轴的右边,从而排除掉近一半的元素(根据快排性能);假如枢轴下标比n-k大,则说明要找的第K大的数在枢轴的左边,继续只对左边的子序列进行快排即可,排除右边子序列。
O(n)时间内找到第K大的数
//利用快排逻辑实现O(n)找到无序序列第K大的数 2024-02-14 #include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> using namespace std; const int MAXN = 1000 + 10; int arr[MAXN]; //确定序列的枢轴元素,并遍历使枢轴左边的元素都比它小,右边都比它大 int Partition(int left, int right) { int pos = left + rand() % (right - left + 1);//得到[left, right]区间内的一个随机数 swap(arr[left], arr[pos]); //保证快排性能 int pivot = arr[left];//枢轴 while(left < right) { while(left < right && arr[left] < arr[right]) { right--; } swap(arr[left], arr[right]); while(left < right && arr[left] < arr[right]) { left++; } swap(arr[left], arr[right]); } return left; } int QuickSort(int left, int right, int m)//m表示n-k,即要找的元素在有序序列中最终的下标 { if(left < right) { int position = Partition(left, right); if(position == m) { return position; // 枢轴元素最终位置,对应数组下标 } else if(position < m) { return QuickSort(position + 1, right, m); } else{ return QuickSort(left, position - 1, m); } }//只要K比n小,就一定能够找到,不会找不到 } int main() { int n; int k;//找到第K大的数 scanf("%d%d", &n, &k); for(int i=0; i<n; i++) { scanf("%d", &arr[i]); } int ret = QuickSort(0, n-1, n-k); printf("第%d大的数为:%d\n", k, arr[ret]); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具