PAT甲级真题1171 Replacement Selection(置换选择) 双解法 带注释

置换选择排序分析
手写小根堆
解法一:手写小根堆 模拟

#include <iostream>
#include <cstring>
#include <algorithm>
#include <set>
#include <queue>
#include <vector>
using namespace std;
/*
对于所有输入
先将前m个数 存入小根堆
从第m+1个数开始遍历 
设t为小根堆堆顶 cnt为小根堆内元素数量
idx代表当前归并段编号 a[i]为当前遍历到的数
如果a[i]>=t 意味着a[i]可以并入当前归并段
t弹出存入当前归并段 a[i]存入小根堆

如果a[i]<t 意味着a[i]属于下一个归并段
t弹出存入当前归并段 a[i]存储在小根堆之外
注意 此时小根堆内的元素数量cnt-=1

我们每一轮都要弹出小根堆堆顶
但是 由于a[i]<t的情况存在 cnt是会归零的
意味着没有元素可以弹出了
此时 存储在小根堆之外的元素 就可以组成一个新的小根堆
他们属于同一个归并段 idx++

因此 在每一次弹出小根堆堆顶前 都要检查小根堆是否为空
若为空 将存在小根堆之外的数组成一个小根堆,idx++

这里提供一个偷鸡的实现方式
heap[1]=heap[cnt--],down(1)//实现堆顶元素的弹出
heap[cnt+1]=a[i]//还是存在heap数组 但是不在小根堆内
然后对于cnt=0的时候 要将其组成一个小根堆
for(int i=cnt/2;i>=1;i--) down(i)
进行初始化就好了
*/
int n,m;
int a[100010];
int mp[100010];
int heap[100010],cnt;
void down(int u){
    int t=u;
    if (u*2<=cnt&&heap[t]>heap[u*2]) t=u*2;
    if (u*2+1<=cnt&&heap[t]>heap[u*2+1]) t=u*2+1;
    if (t!=u) {
        swap(heap[u],heap[t]);
        down(t);
    }
}
void up(int u){
    while (u>1&&heap[u]<heap[u/2]) {
        swap(heap[u],heap[u/2]);
        u/=2;
    }
}
int main (){
    scanf("%d%d", &n,&m);
    for (int i=1;i<=n;i++) {
        scanf ("%d",&a[i]);
        if (i<=m) {//插入堆
            heap[++cnt]=a[i];
            up(cnt);
        }
    }
    int idx=0;//
    int last;
    vector<vector<int>> ans(100010);//答案存储在这个二维数组 idx为下标
    for (int i=m+1;i<=n;i++){
        if (!cnt) {//因为每一轮都要弹出 所以要进行判断
            cnt=m,idx++;
            for (int i=cnt/2;i>=1;i--) down(i);
        }
        int t=heap[1];//堆顶
        if (a[i]>=t) {//a[i]是本归并段
            heap[1]=a[i],down(1);//a[i]插入小根堆
//实际上是将a[i]放在弹出后空缺的堆顶 因此cnt无变化
        }
        else {//a[i]是下一个归并段的
            heap[1]=heap[cnt--],down(1);//弹出堆顶
            heap[cnt+1]=a[i];//a[i]存在小根堆之外
        }
        ans[idx].push_back(t);//t都是存入当前归并段的
    }
//此时循环结束 还有m个数在heap数组中
//其中1~cnt同属一个归并段 但是是否与idx为一个归并段不清楚 
//cnt+1~m属于1~cnt后面的归并段
    if (!cnt) {//涉及heap[1]是否属于当前归并段 看cnt
        cnt=m,idx++;
        for (int i=cnt/2;i>=1;i--) down(i);
    }
    for (int i=1;i<=cnt;i++) ans[idx].push_back(heap[i]);
    if (cnt<m) idx++;//cnt+1~m属于1~cnt后面的归并段
    for (int i=cnt+1;i<=m;i++) ans[idx].push_back(heap[i]);
    
    for (int i=0;i<=idx;i++){//输出答案 注意回车空格
        if (i)puts("");
        sort(ans[i].begin(),ans[i].end());
        for (int j=0;j<ans[i].size();j++){
            if (j) printf (" ");
            printf ("%d",ans[i][j]);
        }
    }
    return 0;
}

解法二:双数组存储模拟
参考自

#include <iostream>
#include <cstring>
#include <algorithm>
#include <set>
#include <queue>
using namespace std;
// 对于一个序列中的数 其不是当前归并段就是下一个归并段的
// 以弹出数的数量为标记
// 每一轮以是否遍历完了原始序列进行判断
// 未遍历完 将a[i]存入对应归并段
// 每一轮都将堆顶存入now归并段
// 操作完后检查堆是否为空
// 若为空 说明当前归并段气数已尽
// 将next归并段全部存入小根堆
// next now数组clear
// 新的循环开始
int n,m;
int a[100010];
priority_queue <int,vector<int>,greater<int>> q;
int main (){
    scanf("%d%d", &n,&m);
    for (int i=1;i<=n;i++) scanf ("%d",&a[i]);
    for (int i=1;i<=m;i++) q.push(a[i]);
    vector <int> now,next;//本归并段 下一个归并段
    int cnt=0;
    int idx=m+1;
    while (cnt<n){
        int t=q.top();
        q.pop();
        cnt++;
        now.push_back(t);
        if (idx<=n){//数组未遍历完
            if (t<=a[idx]) {//属于一个归并段 存入堆
                q.push(a[idx++]);
            }
            else {//属于下一个归并段
                next.push_back(a[idx++]);
            }
        }
        if (q.empty()){//恒纪元已经结束嘞!
            sort(now.begin(),now.end());
            for (int i=0;i<now.size();i++){//输出本轮
                if (i) printf (" ");
                printf ("%d",now[i]);
            }
            printf ("\n");
            now.clear();
            for (auto x:next) q.push(x);
            next.clear();
        }
    }
    return 0;
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值