PAT刷题(六)

本文详细介绍了C++实现Prim和Kruskal算法解决最小生成树问题,包括代码实现、复杂度分析及适用场景。通过实例展示了两种算法在LeetCode和PAT题目中的应用,并探讨了拓扑排序和滑动窗口在不同问题中的应用策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

随时更新,遇到好题就更新!
这次我主要练习C和C++(需要使用STL就用C++),练习网站有两个:
codeupPTA

这个专题同样主要是图的相关题

最小生成树

以codeup中《还是畅通工程》这题为例

prim算法

prim算法和Dijkstra算法非常类似,代码如下,区别在代码中标出来了:

#include <iostream>
#include <stdio.h>
using namespace std;
const int maxn=110;
const int INF=100000000;
int graph[maxn][maxn];
int ans=0;
int d[maxn];
bool visit[maxn];
int N;
void prim(){
    fill(d,d+maxn,INF);
    fill(visit,visit+maxn,0);
    d[1]=0;
    for(int i=0;i<N;i++){
        int u=-1,MIN=INF;
        for(int j=1;j<=N;j++){
            if(visit[j]==false&&d[j]<MIN){
                MIN=d[j];
                u=j;
            }
        }
        if(u==-1)return;
        visit[u]=true;
        ans+=d[u];
        for(int j=1;j<=N;j++){
            if(visit[j]==false&&graph[u][j]!=INF){
                if(graph[u][j]<d[j]){//与Dijkstra算法的区别在这,反映了d的含义不同
                    d[j]=graph[u][j];//Dijkstra算法中d是指到起点的距离;
                    //而prim中d是指到集合(visit为true的点的集合)间的距离
                }
            }
        }
    }
}
int main(){
    while(scanf("%d",&N)!=EOF){
        if(N==0)break;
        fill(graph[0],graph[0]+maxn*maxn,INF);
        int inputNum=N*(N-1)/2;
        for(int i=0;i<inputNum;i++){
            int c1,c2,dis;
            scanf("%d%d%d",&c1,&c2,&dis);
            graph[c1][c2]=dis;
            graph[c2][c1]=dis;
        }
        prim();
        printf("%d\n",ans);
        ans=0;
    }
}

kruskal算法

kruskal算法主要利用了贪心的思想,中间辅以并查集来判断是否选择,代码如下:

#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
const int maxn=5010;
struct Edge{
    int c1,c2,dis;
}edge[maxn];
int N;
int father[maxn];
int findFather(int x){
    int a=x;
    while(x!=father[x]){
        x=father[x];
    }
    while(a!=father[a]){
        int z=a;
        a=father[a];
        father[z]=x;
    }
    return x;
}
int ans;
bool cmp(Edge e1,Edge e2){
    return e1.dis<e2.dis;
}
int inputNum;
int recordNum;
void kruskal(){
    recordNum=0;
    ans=0;
    for(int i=0;i<inputNum;i++){
        int faA=findFather(edge[i].c1);
        int faB=findFather(edge[i].c2);
        if(faA!=faB){
            ans+=edge[i].dis;
            father[faA]=faB;
            recordNum++;
            if(recordNum==N-1)break;
        }
    }
}
int main(){
    while(scanf("%d",&N)!=EOF){
        if(N==0)break;
        for(int i=1;i<=N;i++){
            father[i]=i;
        }
        inputNum=N*(N-1)/2;
        for(int i=0;i<inputNum;i++){
            int city1,city2,distance;
            scanf("%d%d%d",&city1,&city2,&distance);
            edge[i].c1=city1;
            edge[i].c2=city2;
            edge[i].dis=distance;
        }
        sort(edge,edge+inputNum,cmp);
        kruskal();
        printf("%d\n",ans);
    }
}

适用情形

prim算法复杂度O(V^2),V为点数
kruskal算法复杂度O(ElogE),E为边数(复杂度体现在边的排序上)
prim算法适合边多的稠密图,而kruskal算法适用于边少的稀疏图

leetcode 1584.连接所有点的最小费用

prim

代码如下:

class Solution {
public:
    int d[1010];
    int INF=100000000;
    bool visit[1010];
    int ans=0;
    void prim(int n,vector<vector<int>>& points){
        fill(d,d+1010,INF);
        d[0]=0;
        for(int i=0;i<n;i++){
            int u=-1,MIN=INF;
            for(int j=0;j<n;j++){
                if(visit[j]==false&&d[j]<MIN){
                    MIN=d[j];
                    u=j;
                }
            }
            if(u==-1)return;
            visit[u]=true;
            ans+=d[u];
            for(int j=0;j<n;j++){
                int uj=abs(points[u][0]-points[j][0])+abs(points[u][1]-points[j][1]);
                if(visit[j]==false&&uj<d[j]){
                    d[j]=uj;
                }
            }
        }
    }
    int minCostConnectPoints(vector<vector<int>>& points) {
        prim(points.size(),points);
        return ans;
    }
};

kruscal

代码如下:

class Solution {
public:
    struct Edge{
        int p1,p2,dis;
    }edge[500010];
    int getDis(vector<int>&po1,vector<int>&po2){
        return abs(po1[0]-po2[0])+abs(po1[1]-po2[1]);
    }
    int father[1010];
    static bool cmp(Edge e1,Edge e2){
        return e1.dis<e2.dis;
    }
    int ans=0;
    int n=0;
    int m=0;
    int record=0;
    int findFather(int x){
        int a=x;
        while(x!=father[x]){
            x=father[x];
        }
        while(a!=father[a]){
            int z=a;
            a=father[a];
            father[z]=x;
        }
        return x;
    }
    void Kruscal(){
        for(int i=0;i<n;i++){
            father[i]=i;
        }
        for(int i=0;i<m;i++){
            int faA=findFather(edge[i].p1);
            int faB=findFather(edge[i].p2);
            if(faA!=faB){
                father[faA]=faB;
                ans+=edge[i].dis;
                record++;
                if(record==n-1)break;
            }
        }
    }
    int minCostConnectPoints(vector<vector<int>>& points) {
        n=points.size();
        int len=points.size();
        for(int i=0;i<len;i++){
            for(int j=i+1;j<len;j++){
                edge[m].p1=i;
                edge[m].p2=j;
                edge[m].dis=getDis(points[i],points[j]);
                m++;
            }
        }
        sort(edge,edge+m,cmp);
        Kruscal();
        return ans;
    }
};

这个很明显用时要长很多

拓扑排序

PAT A 1146 Topological Order

代码如下,思路就是针对每个序列模仿拓扑排序的过程,每移一位,先判断入度是否为0,若是,就要将该点出发的边所连的点的入读减一;若不是则该序列不是拓扑排序的序列:

#include <iostream>
#include <stdio.h>
#include <vector>
#include <queue>
using namespace std;
const int maxn=1010;
vector<int>graph[maxn];
vector<int>ans;
vector<int>seq[maxn];
int N,M,indegree[maxn],K;
void topological(){
    for(int i=0;i<K;i++){
        vector<int>temp=seq[i];
        vector<int>tempindegree;
        for(int k=0;k<=N;k++)tempindegree.push_back(indegree[k]);
        int j=0;
        for(;j<N;j++){
            int node=temp[j];
            if(tempindegree[node]==0){
                int tempLen=graph[node].size();
                for(int h=0;h<tempLen;h++){
                    tempindegree[graph[node][h]]--;
                }
            }else{
                break;
            }
        }
        if(j!=N)ans.push_back(i);
    }
}
int main(){
    scanf("%d%d",&N,&M);
    fill(indegree,indegree+N+1,0);
    for(int i=0;i<M;i++){
        int in,out;
        scanf("%d%d",&in,&out);
        indegree[out]++;
        graph[in].push_back(out);
    }
    scanf("%d",&K);
    for(int i=0;i<K;i++){
        for(int j=0;j<N;j++){
            int temp;
            scanf("%d",&temp);
            seq[i].push_back(temp);
        }
    }
    topological();
    int ansSize=ans.size();
    for(int i=0;i<ansSize;i++){
        printf("%d",ans[i]);
        if(i<ansSize-1)printf(" ");
    }
}

滑动窗口

leetcode 1004. 最大连续1的个数 III

代码如下,思路是滑动窗口,统计窗口内1出现的次数,如果窗口大小-1的个数>K,说明此时K次操作不能弥补0出现的过多了,所以此时窗口需要缩小(滑动窗口中,窗口扩大由right右移;窗口缩小由left右移),所以此时left要右移:

class Solution {
public:
    int longestOnes(vector<int>& A, int K) {
        //滑动窗口
        int left=0,right=0;
        int len=A.size();
        int record=0;
        int ans=0;
        while(right<len){
            if(A[right]==1)record++;
            if(right-left+1-record>K){
                if(A[left]==1)record--;//细节
                left++;
            }
            right++;
            ans=max(ans,right-left);
        }
        return ans;
    }
};

leetcode 424. 替换后的最长重复字符

代码如下,这题和上一题非常类似,这个思路是这样的,由上面统计窗口内1的最大个数,到统计窗口内出现次数最多的字母的个数:

class Solution {
public:
    int characterReplacement(string s, int k) {
        int left=0,right=0;
        int ans=0;
        int dis=0;
        int len=s.length();
        int alpha[26]={0};
        while(right<len){
            alpha[s[right]-'A']++;
            ans=max(ans,alpha[s[right]-'A']);//细节
            if(right-left+1-ans>k){
                alpha[s[left]-'A']--;
                left++;
            }
            right++;
            dis=max(dis,right-left);
        }
        return dis;
    }
};

leetcode 239. 滑动窗口最大值

好家伙这道题。一开始超时,后来直接各种错误,借助官方题解终于写出一版我比较认同的了,代码如下:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int left=0,right=0;
        int len=nums.size();
        vector<int>record;
        priority_queue<pair<int,int>>q;
        while(right<len){
            q.emplace(nums[right],right);
            if(right-left+1>=k){
                record.push_back(q.top().first);
                while(q.top().second<=left&&!q.empty())q.pop();
                left++;
            }
            right++;
        }
        return record;
    }
};

思路是:
滑动窗口的基本模板,然后我们需要找出的是在这个窗口中的最大值,也就是当前窗口大小==k时,我们需要找出此时的最大值,然后前移窗口,但是此时前移窗口如何获取前移后的最大值呢?
如果把right调到left的位置后会超时,而如果什么都不做的话我们无法直到当前窗口中前K-1个元素中那个是最大的,所以我们需要实时记录最大值状态,然后不断更新,那么优先队列这个数据结构就非常好
那当窗口前移时优先队列该如何做呢?看几个例子:

input:
[9,10,9,-7,-4,-8,2,-6]
5
output:
[10,10,9,2]
  • 优先队列的top值处在left位置时肯定要pop
  • left处的值也要pop,否则会输出[10,10,9,9],我一开始就没想明白这里到底应该如何做,用下标来判断就非常好了!

综上就要循环判断while(q.top().second<=left)此时要对top值进行pop,这里一个很妙的就是将值的下标作为判断,我一开始没有考虑到这点

另外针对例子

input
[1]
1
outpu
[1]

还要加上!q.empty()的限制,否则会死循环!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值