[数据结构]第五章-排序与选择

排序
  就地/非就地
  稳定/不稳定(如 5’, 3 , 5 排序后变为 3 , 5 , 5’为不稳定)

·O(n^2) 的排序:

·简单排序算法
(但 直接插入排序最好情况(有序时)为O(n))

·O(nlogn)的排序:

·快速排序
(Partition操作复杂度O(n) -> 借鉴此操作的算法,如O(n)时间将负数放左边正数放右边等)
(若每次划分都恰好得到两个大小相等的区域则复杂度T(n) = 2T(n/2)+O(n)= O(nlogn))
(时间复杂度与基准值的选取有关,最坏情况(恰好都选最值时)为O(n^2),要保证O(nlogn)的复杂度则需要较好的基准值选取 -> 可考虑O(n)时间的选择算法)
(辅助空间O(logn)(栈空间))
快排是一种不稳定的排序。

·合并排序
(辅助空间O(n))

·堆排序
(辅助空间O(1))

·线性时间的排序(特殊有序集)

自己总结的几种常见排序的详细算法

//–作业题:

5.3 逆序对

这里写图片描述

(每次交换相邻元素将原序列排序为非递减,问题等价于求这个序列的逆序对数。比如每次考虑这串序列的最大值,要把这个数挪到最后的位置要挪动的最小次数显然是在它此后比它小的数的个数,弃掉这个已在正确位置上的数继续考虑剩余序列的最大值挪动同理,于是可知这个等价是成立的。那么要怎么求一串数的逆序对数呢。如果是冒泡那样的一个个比对的O(n^2)在这里会T掉小半数据,下面用的是O(nlogn)的归并排序。归并的主要思想是,如果这串数字已被分成左右两个有序的子段,每次比较两子串的首个元素,如果右边首元素小于左边,那么则小于整个左段,cnt+=左端个数,把这个当前最小值存到另一个数组中。如果不小于则直接把左边首元素存起来。最后比较到一个子段已经没有元素了,就把剩下的子段的全部元素存下,这时候就得到了一个排好序的数组,然后把它拷贝回原数组。得到有序子段的方法是一个规模变小的子问题,用递归实现。递归的退出条件是这段里已只有一个元素。)

#include<cstdio>  
#include<iostream>  
using namespace std;  
#define MAX 50000  

int a[MAX+10];  
int b[MAX+10];  
int cnt = 0;  

void merge(int l,int r)  
{  
    if(l >= r-1) return;  

    int m = (l+r)/2;  
    merge(l,m);  
    merge(m,r);  

    int i = l, j = m, k = l;  
    while(i < m && j < r)  
    {  
        if(a[i] > a[j])  
        {  
            b[k++] = a[j++];  
            cnt += m-i;  
        }  
        else b[k++] = a[i++];  
    }  

    while(i < m) b[k++] = a[i++];  
    while(j < r) b[k++] = a[j++];  

    for(int x = l; x < r; x++)  
        a[x] = b[x];  
}  

int main()  
{   
    int n;  
    scanf("%d",&n);  

    for(int i = 0; i < n; i++)  
        scanf("%d",&a[i]);  

    merge(0,n);  
    printf("%d\n",cnt);  

    return 0;  
} 

5.2 Missile

这里写图片描述

(开始想到的思路是贪心,每次计算一下这个点到左侧和右侧的距离alen,blen,取较小的那个拿来更新下amax或bmax,这样的做法其实是不对的,比如在有三个点它们和左右圆心的距离分别为(80,12),(60,20),(60,80),这样按贪心的思路得到的ans为80,其实只要72就够了。这里采用的做法是,把这些点按到左圆心的距离从大到小排序,每次考虑是不是要扔掉第一个点扔给右边的圆去包括,更新下当前的ans。)

#include<cstdio>  
#include<iostream>  
#include<algorithm>  
using namespace std;  
#define MAX 100000  
#define INF 1000000000  

int myMax(int a,int b){return a>b?a:b;}   
int myMin(int a,int b){return a<b?a:b;}   
int sqr(int x) {return x*x;}  
int x[MAX+10];  
int y[MAX+10];  
int i;  

struct Node  
{  
    int id;  
    int num;  
}dis[MAX+10];  

bool cmp(const Node &p1,const Node &p2)  
{  
    return p1.num>p2.num;  
}  

int main()  
{  

    int x1,y1,x2,y2,n;     
    scanf("%d %d %d %d %d",&x1,&y1,&x2,&y2,&n);    

    for(i = 0; i < n; i++)  
    {  
        scanf("%d %d", &x[i], &y[i]);  
        dis[i].id = i;  
        dis[i].num = sqr(x[i]-x1)+sqr(y[i]-y1);   
    }  

    sort(dis,dis+n,cmp);  

    int d = INF;  
    int r2 = 0;  
    for(i = 0; i < n; i++)  
    {  
        d = myMin(d,r2+dis[i].num);  
        int temp = sqr(x[dis[i].id]-x2)+sqr(y[dis[i].id]-y2);  
        r2 = myMax(r2,temp);  
    }  

    printf("%d\n",d<r2?d:r2);  

    return 0;  
}

——
练习题:

5.1 最小和
这里写图片描述

(最朴素的O(n^3)消去一个n的做法是利用前缀和,记录1~i的和sum数组再求差比较这样O(n^2)依然T近半。可知这些sum一一做差得到的就是所有子段和,要使得子段绝对值最小也即两个做差的sum最接近。于是排个序nlogn再遍历一遍更新最小值即可。)

#include<cstdio>  
#include<iostream>  
#include<algorithm>  
using namespace std;  
#define LL __int64  
#define MAX 100000  
#define INF 10000000007  

LL a[MAX+10];  
LL sum[MAX+10];  
int i,j;  

int main()  
{  
    int n;  
    scanf("%d",&n);  

    LL ans = INF;  
    for(i = 1; i <= n; i++)  
    {  
        scanf("%I64d",&a[i]);  
        sum[i] = sum[i-1]+a[i];  
    }  

    sort(sum,sum+n+1);  

    for(i = 1; i <= n; i++)  
    {  
        LL temp = sum[i]-sum[i-1]?(sum[i]-sum[i-1]):(sum[i-1]-sum[i]);  
        if(ans > temp) ans = temp;  
    }   

    printf("%I64d\n",ans);  

    return 0;     
}

5.4 数数

这里写图片描述

(这里用map来存很简单,不懂出题人的本意是要怎么打0 0)

#include<iostream>  
#include<cstdio>  
#include<map>  
#define LL __int64  
using namespace std;  

map<LL,int> m;  
map<LL,int> :: iterator it;  

int main()  
{  
    int n;  
    scanf("%d",&n);  

    for(int i = 0; i < n; i++)  
    {  
        LL num;  
        scanf("%I64d",&num);  
        m[num]++;  
    }  
    for(it = m.begin(); it!=m.end(); it++)  
        printf("%I64d %d\n",it->first,it->second);  
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值