1 算法思想
这里将寻找最小/大的前k个数,寻找逆序对,线性时间选择(寻找第k小/大的元素),奇偶/大小写字符分别放在前后部分等和排序相关类型的题目,放在了排序而不是查找中。
1.1含义
排序含义:重新排列列表中元素,使其递增或递减。
算法稳定性:若两个元素Ri,Rj对应值相等,经过排序后,Ri仍然在Rj前面,则说明该排序算法是稳定的。
1)插入排序
思想:将待排序元素插入到前面已经排好序的序列中
直接插入排序:
将元素L(i)插入到已经有序的子序列L[1…i-1]中,查找出L[i]在L[1…i-1]中的插入位置k,将L[k…i-1]中所有元素后移一个位置,令L[k]=L[i]
折半插入排序:
在直接插入排序的基础上,查找待插入位置时采用二分查找
希尔排序:
希尔排序是插入排序的变体,设置步长依次为n/2, n/4, ...,1,每次对步长上的元素进行插入排序。
2)选择排序
思想:第i趟在后面n-i+1个剩余未排序的所有元素中选择最小的元素作为序列中第i个元素。
简单选择排序:
第i趟排序从L[i…n]中选择关键字最小的元素与L[i]交换,每趟排序确定一个元素的最终位置
堆排序:
是树形选择排序。在排序过程中,将L[1..n]看成是完全二叉树的顺序存储结构,利用完全二叉树中父子之间关系,选择关键字最大/小的元素。
大顶堆:任意父节点大于左右孩子节点,可用于生成递增的排序数组。可以解决求取最小的k个数的问题,因为堆顶是k个数中最大的,如果待比较元素小于堆顶元素,则可以用待比较元素替换大顶堆的堆顶元素。
建堆:从n/2到1下标的元素,不断判断当前结点与左右孩子的值,若左右孩子值中的较大者大于父节点的值,则交换孩子结点与父节点
调整: 每趟将堆顶元素与数组末尾元素交换,然后将除数组末尾的剩余元素部分再次调用建堆过程,使得剩余元素能成为大顶堆,重复上述过程,直到只有一个元素。
3)交换排序
思想:根据两个元素的大小来交换两个元素在序列中的位置
冒泡排序:从后向前两两比较相邻元素的值,若为逆序,则交换他们,一趟结束后,将最大元素放置数组末尾。
快速排序:基于分治,将待排序标L[1…n]中选取一个元素pivot作为枢轴,通过一趟排序将待排序标划分为L[1…k-1]和L[k+1,…n],
使得L[1…k-1]中所有元素小于pivot.L[k+1…n]中所有元素大于或等于pivot。
4)其他排序
归并排序:
将两个或两个以上的有序表组合成一个新的有序表。将n个记录的数组,看成n个有序的子表,两两归并,得到n/2个有序表,再两两归并,直到合并为一个长度为n的有序表为止。
基数排序:
第一趟选择个位,第二趟选择10位,...每一趟将数字上该位对应的值放入到对应编号的桶中,最后输出桶的结果即可。
1.2特点
1.3应用
快速排序的应用主要包括:查找第k大/小的元素
归并排序的应用主要包括: 逆序对等
堆排序的应用主要包括:小顶堆用于寻找前k个最大的元素,大顶堆用于寻找前k个最小的元素等。
1.4通用解法
排序算法 1 弄清楚各个排序算法的思想,重点关注:快速排序,堆排序和归并排序。 |
1.5经典例题讲解
快速排序
int randomInRange(int min,int max) { return (rand()%(max - min + 1) + min);//牛逼,用除以这个范围内的数,加上min来得到min~max之间任意数 }
int partition(int *A,int low,int high) { int iIndex = randomInRange(low,high); //int iPos = A[iIndex]; swap(&A[low],&A[iIndex]);//交换第一个数与枢轴 int iPos = A[low]; while(low<high)//注意low<high,low=high会死循环 { //后面>枢轴,则一直递减高位的指标 //while(A[high]>iPos)//漏了low<high的判断条件,否则一直死循环 while(low<high&&A[high]>= iPos) { high--; } //后面<枢轴,这样确保A[low]一定是< iPos,用于下面while(low<high&&A[low]<= iPos)循环,确保该循环一开始可以跳过刚才交换后的元素 A[low] = A[high]; while(low<high&&A[low]<= iPos) { low++; } A[high] = A[low]; } A[low] = iPos;//易错,不能忘记对枢轴位置的元素进行正确赋值,随机选枢轴这步应该不需要了 return low;//返回当前这一趟排序的枢轴位置 }
void quickSort(int *A,int low,int high) { if(low<high)//注意low<high,low<=high,死循环 { //printf("Enter quickSort\n"); int iPos = partition(A,low,high);//0 quickSort(A,low,iPos-1);//0,0 quickSort(A,iPos+1,high); } } |
2 排序系列
类别-编号 |
题目 |
遁去的一 |
1 |
排序 对输入的n个数进行排序并输出 |
计算机考研—机试指南 https://blog.csdn.net/qingyuanluofeng/article/details/47159077 用快速排序实现,参考经典例题解析 |
2 |
成绩排序
输入: 3 abc 20 99 bcd 19 97 bed 20 97 输出: bcd 19 97 bed 20 97 abc 20 99 |
计算机考研—机试指南 https://blog.csdn.net/qingyuanluofeng/article/details/47159193 易错点: 1对于字符指针,scanf("%s",ps[i].sName)不需要取地址符 2对于名字,要指定大小,否则内存访问非法,char sName[128];而不是定义char* sName; 3int strcmp(const char* string1,const char* string2); <0:前<后
代码: typedef struct Student { //char* sName;//就是这里出错了,没有具体制定多大
//法二,用重载操作符operator<的方法做 bool operator < (const Student& stu) const { if(iGrade != stu.iGrade) { return iGrade < stu.iGrade; } else { if(sName != stu.sName) { return strcmp(sName,stu.sName) < 0 ? true:false; } else { return iAge < stu.iAge; } } } char sName[128]; int iAge; int iGrade; }Student;
int partition(Student* ps,int low,int high) { Student stu = ps[low]; while(low < high) { //while(low < high && compare(stu,ps[high]))//法1 while(low < high && stu < ps[high]) { high--; } ps[low] = ps[high]; //while(low < high && compare(ps[low],stu))//法1 while(low < high && ps[low] < stu) { low++; } ps[high] = ps[low]; } ps[low] = stu; return low; }
void quickSort(Student* ps,int low,int high) { if(low < high) { int iPos = partition(ps,low,high); quickSort(ps,low,iPos-1); quickSort(ps,iPos+1,high); } }
int main(int argc,char* argv[]) { int iNum; while(EOF!=scanf("%d",&iNum)) { Student* ps = (Student*)malloc(iNum*sizeof(Student)); for(int i = 0; i < iNum; i++) { //scanf("%s %d %d",&ps[i].sName,&ps[i].iAge,&ps[i].iGrade); //关键字符串不需要取地址,因为它本身就是一个地址,否则就是取地址的地址了 scanf("%s %d %d",ps[i].sName,&ps[i].iAge,&ps[i].iGrade); } quickSort(ps,0,iNum-1); print(ps,iNum); free(ps); } return 0; } |
3 |
Sort 给n个整数,按从大到小的顺序,输出前m大的整数
输入: 5 3 3 -35 92 213 -644 输出: 213 92 3 |
计算机考研—机试指南 https://blog.csdn.net/qingyuanluofeng/article/details/47159653 思路: 先按从小到大用快排排好序,然后输出排好序的数组从最后开始输出m个即可 关键: 1 已经达到千万数量级,1秒不能解决,必须用哈希,因为数字的范围达到百万级 2 哈希针对的是输入数值处于特定范围的问题,建立一个范围大小的数组,建立hash[x] = x出现多少次的映射 3 对于定义较大容量的数组,放在函数体外,这样用全局变量,内存会比较充足 4 对于区间为负的,需要设定下标补偿值
代码: #define POS 500000 int iHash[1000001] = {0};
int main(int argc,char* argv[]) { int n,m,i; while(EOF!=scanf("%d%d",&n,&m) && m <= n && m > 0 && m < 1000000 && n > 0 && n < 1000000 ) { for(i = 0; i < n ;i++) { int iValue; scanf("%d",&iValue); if(iValue < -500000 || iValue > 500000) { return 0; } else { iHash[iValue + POS] = 1;//下标和值存在某种一元函数关系 } } for(i = 500000 ; i >= -500000 ; i--) { if(iHash[i + POS]==1) { m--; printf("%d ",i); } //if(m)//应该是m=0打印空格 if(0==m) { printf("\n"); break; } } //printf("\n"); } system("pause"); getchar(); return 0; } |
4 |
最小的k个数 输入n个整数,找出其中最小的k个数。例如输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
输入: 每个测试案例包括2行: 第一行为2个整数n,k(1<=n,k<=200000),表示数组的长度。 第二行包含n个整数,表示这n个数,数组中的数的范围是[0,1000 000 000]。 输出: 对应每个测试案例,输出最小的k个数,并按从小到大顺序打印。 样例输入: 8 4 4 5 1 6 2 7 3 8 10 5 1 9 8 6 4 1 0 4 3 5 样例输出: 1 2 3 4 0 1 1 3 4 |
剑指offer https://blog.csdn.net/qingyuanluofeng/article/details/39138417 关键: 1 利用划分函数,对下标为k的数字进行划分,那么最左边的k各数字一定是最小的k个 数字,但是不一定有序。 2 while(iIndex != k-1 )//注意,这里不是iIndex = k,因为数组的第k-1下标,表示数组中第k个数 3 O(nlogK)的算法,适合处理海量数据(适合n很大,k很小) 先创建一个大小为k的数据容器来存储最小的k个数字,每次从输入的n个整数中读入一个数。如果容器中已有的数字 少于k个,直接把这次读入的整数放入容器中。如果容器中已经有k个数,此时只能替换原有数字。找出k个数字中 的最大值,然后依次拿待插入的整数和最大值进行比较,若待插入的小,就替换。 4 vector<int>::const_iterator itV;//对于常量容器,必须要用常量迭代器 5 if(setLeastK.size() < k)//如果存储最小k个元素的容器大小<K,则直接加入 { setLeastK.insert(*itV); } else//如果已经达到k个元素,则找出其中的最大元素,删除该最大元素后,再加入新的小元素 6int low = 0,high = iLen -1;//注意,这里做划分的函数的下标必须能取到 7 greater表示内置类型从大到小排序,less表示内置类型从小到大排序。所有greater得到的是大顶堆
代码: const int MAXSIZE = 200001; int iArr[MAXSIZE];
int randomInRange(int min,int max) { return (rand() % (max - min + 1) + min); }
void swap(int* pNum1,int* pNum2) { int iTemp = *pNum1; *pNum1 = *pNum2; *pNum2 = iTemp; }
int partition(int low,int high) { int iIndex = randomInRange(low,high); swap(&iArr[low],&iArr[iIndex]); int iAxis = iArr[low]; while(low < high) { while(low < high && iArr[high] >= iAxis) { high--; } iArr[low] = iArr[high]; while(low < high && iArr[low] <= iAxis) { low++; } iArr[high] = iArr[low]; } iArr[low] = iAxis; return low; }
void minKNum(int iLen,int k) { int low = 0,high = iLen -1;//注意,这里做划分的函数的下标必须能取到 int iIndex = partition(low,high); while(iIndex != k-1 )//注意,这里不是iIndex = k,因为数组的第k-1下标,表示数组中第k个数 { if(iIndex > k-1 ) { high = iIndex - 1; } else { low = iIndex + 1; } iIndex = partition(low,high); } for(int i = 0 ; i < k ; i++) { if(i) { printf(" %d",iArr[i]); } else { printf("%d",iArr[i]); } } printf("\n"); }
//用vector来存储数据,用multiset来存储最小的k个数,注意这里用greater<int>来确保容器中的第一个数字是最大的数字 // greater表示内置类型从大到小排序,less表 |