一、程序设计中常用算法
洛谷排序题
这个题目是排序的模板题,对于复杂度O(n^2)
的排序算法,可以过三个样例,其余TLE,
对于复杂度稳定在O(nlogn)
的排序算法,才会AC。
插入排序
思想史,遍历数组中的每一个数a[i],i
之前的是有序的,i
之后的是无序的,a[i]
寻找合适的位置插入有序的序列中。插入排序就是不断的将有序合并无序的过程。
{2,4,5} a[i]=3 { 10,1,8}
一趟排序后:{2,3,4,5} a[i]=10 {1,8}
代码实现
void insertSort(){//不带哨兵的插入排序容易理解
// 循环到i,i之前是有序的,i在有序中寻找自己的位置,i之后是无序的
for(int i=2;i<=n;i++){
for(int j=i-1;j>0;j--){
if(a[j]>a[j+1]) swap(a[j],a[j+1]);
else break;
}
}
}
使用哨兵的实现
void insertSort(){
// 循环到i,i之前是有序的,i在有序中寻找自己的位置,i之后是无序的
for(int i=2;i<=n;i++){
a[0]=a[i];//哨兵
int j=i-1;//从上一个开始遍历
for(;j>0;j--){
if(a[j]>a[0]) a[j+1]=a[j];//全部都是与哨兵比较
else break;//直到找到哨兵应该在的位置
}
a[j+1]=a[0];
}
}
插入排序优点在于可以实现链式存储的排序,对于双向指针的链表是比较容易实现交换的。
冒泡排序
每次通过相邻交换,每趟确定未排序的数字中最大的数字的位置。
洛谷-车厢重组
void BubbleSort(){
for(int i=1;i<=n-1;i++){
for(int j=1;j<=n-i;j++){
if(a[j]>a[j+1]) swap(a[j],a[j+1]);
}
}
}
快速排序
快速排序基于分治思想,任取一元素x
作为枢轴,然后小于x
的放在左边,大于x
的放在x右边,那么x一定放在了最终位置,左右区间的都确定了大概位置。然后依次对左右两个区间继续做该操作,直到整个集合都有序。
如何实现?使用双指针进行交换。
int getP(int low,int high){
int x=a[low];
while(low<high){
while(low<high&&a[high]>=x) high--;//
a[low]=a[high];
while(low<high&&a[low]<=x) low++;
a[high]=a[low];
}
a[low]=x;
return low;
}
void QuickSort(int l,int r){
if(l>=r) return;//退出循环条件
int p=getP(l,r);//确定了p位置
QuickSort(l,p-1);
QuickSort(p+1,r);
}
选择排序
选择排序思想是从无序数组中选择最小的元素放在最前面,直到数组有序。
void selectSort(){
for(int i=1;i<=n-1;i++){
int minn=a[i],p=i;
for(int j=i+1;j<=n;j++){
if(a[j]<minn){
minn=a[j];
p=j;
}
}
swap(a[p],a[i]);
}
}
堆排序
#include<bits/stdc++.h>
using namespace std;
#define N 101010
int n;
int a[N];
//建立大根堆,即每次取出的都是剩余堆最大的,然后取出后放在后面,直到堆空,最终数组成为升序
void update(int k,int len){
a[0]=a[k];
for(int i=k*2;i<=len;i<<=1){
//如果a[k]>min(a[k*2],a[k*2+1])
//那么必须将a[k*2],a[k*2+1]之中!!最大!!的换上去
if(i+1<=len&&a[i]<a[i+1]) i++;//寻找 k*2 与 k*2+1中较大的
if(a[0]<a[i]){//认清是寻找a[0](原a[k]位置)所以是a[0]与a[i]比较
a[k]=a[i];//a[i]升上去
k=i;
}else break;
}
a[k]=a[0];
}
void headSort(){
for(int i=n/2;i>=1;i--) update(i,n);
for(int i=n;i>1;i--){//i==1时 a[1]肯定是最小的了
swap(a[1],a[i]);
update(1,i-1);
}
}
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
headSort();
for(int i=1;i<=n;i++) printf("%d ",a[i]);
}
归并排序
void mergeSort(int l,int r){
if(l>=r) return;
int l1=l,r1=(l+r)/2,l2=(l+r)/2+1,r2=r;
mergeSort(l1,r1);
mergeSort(l2,r2);
int i=l1,j=l2,index=i;
while(i<=r1&&j<=r2){
if(a[i]<a[j]){
tmp[index++]=a[i];
i++;
}else {
tmp[index++]=a[j];
j++;
}
}
while(i<=r1){
tmp[index++]=a[i];
i++;
}
while(j<=r2){
tmp[index++]=a[j];
j++;
}
for(i=l1;i<=r2;i++) a[i]=tmp[i];
}
几种排序算法各有优缺,各有用途,在c++的sort()
实现就是结合了快排、插入、堆排序的方式。
二、排序算法应用
简单的排序题目可能是对一个结构体数组排序,根据结构体的键进行排序,需要重写比较函数。
洛谷-奖学金
#include<bits/stdc++.h>
using namespace std;
#define N 1010
struct Node{
int id,sum,chinese;
}a[N];
int n,c,m,e,sum;
bool cmp(Node n1,Node n2){//比较函数是关键
return n1.sum==n2.sum?(n1.chinese==n2.chinese?(n1.id<n2.id):n1.chinese>n2.chinese):n1.sum>n2.sum;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++){
cin>>c>>m>>e;
sum=c+m+e;
a[i]={i,sum,c};
}
sort(a+1,a+1+n,cmp);
for(int i=1;i<=5;i++) cout<<a[i].id<<' '<<a[i].sum<<endl;
}
洛谷-宇宙总统 两个高精度数值比较,使用string
存储,先比较位数,然后再比较string
字典序。
离散化
中位数
动态维护中位数
第k大数
在快速排序中,每次选取枢轴mid
(一般为最左端点),假设枢轴排序后最终位置为x
即a[x]=mid
。在区间[l,r]
一趟快速排序后,被分为了三段,a[l,x-1]<=mid
、a[x]=mid
、a[x+1,r]>mid
三段。在求解第k大数中,每经过一次排序,我们都可以锁定第k大数所在区间.若k<x
则 第k大数在区间[l,x-1]
中,若x<k
则 第k大数在区间[x+1,r]
中,递归下去继续寻找,直到 x==k
a[x]=mid=枢轴
就是第k大数。
#include<bits/stdc++.h>
using namespace std;
#define N 5001010
int n,p,a[N];
int getP(int l,int r){
a[0]=a[l];
while(l<r){
while(l<r&&a[r]>=a[0])r--;
a[l]=a[r];
while(l<r&&a[0]>=a[l]) l++;
a[r]=a[l];
}
a[l]=a[0];
return l;
}
void QuickSort(int l,int r){
int k=getP(l,r);
if(p<k) QuickSort(l,k-1);
else if(p>k) QuickSort(k+1,r);
else cout<<a[k];
}
int main()
{
scanf("%d",&n);
scanf("%d",&p);
p++;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
QuickSort(1,n);
}
前K小数的话,堆排序效率较高。维护一个大小为k的堆,初始化可以是前k个元素,建成大根堆。在之后的k+1~n
个元素中,如果a[i]>堆顶
,可以肯定的是该元素一定不是前k小数,否则代替堆顶,更新堆。最终堆剩余元素就是前k小数,复杂度介于O(n)
到O(nlogk)
之间。
逆序对
未完。。。