随时更新,遇到好题就更新!
这次我主要练习C和C++(需要使用STL就用C++),练习网站有两个:
codeup和PTA
这个专题同样主要是图的相关题
文章目录
最小生成树
以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()
的限制,否则会死循环!