目录
一、DFS
AcWing 842. 排列数字
思路:
DFS得到全排列,flag[i]表示i是否已经被用过,当选了n个数时,即是一种方案,并将该方案输出
代码:
import java.io.*;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static int n;
static int N=10;
static int a[]=new int[N];
static boolean flag[]=new boolean[N];
public static void dfs(int u){
if(u==n){//已填满n个数
for(int i=0;i<n;i++) pw.print(a[i]+" ");
pw.println();
return;
}
for(int i=1;i<=n;i++){
if(!flag[i]){//当前数没有被使用过
a[u]=i;
flag[i]=true;
dfs(u+1);
flag[i]=false;
}
}
}
public static void main(String args[]) throws IOException{
n=nextInt();
dfs(0);
pw.close();
}
}
AcWing 843. n-皇后问题
思路:
由于任意两个皇后不能处于同一行、同一列、同一斜线线上,因此开辟三个数组col,dg,udg数组,分别该列、对角线、反对角线是否能放置皇后,只有三个数组全为false时才可放皇后,利用dfs解决;
记绿色为反对角线udg,则每个点映射到y轴上为u-i,由于u-i可能为负数,因此同时加上n,因此用udg[u-i+n]表示第u行第i列所在反对角线是否已经放置过皇后
记蓝色为对角线dg,则每个点映射到y轴上为u+i,因此用dg[u+i]表示第u行第i列所在对角线是否已经放置过皇后
代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
public class Main {
static BufferedReader bf=new BufferedReader(new InputStreamReader(System.in));
static PrintWriter pw=new PrintWriter(System.out);
static int n;
static int N=20;
static char g[][]=new char[N][N];
static boolean col[]=new boolean[N];//看该列是否满足条件
static boolean dg[]=new boolean[N];//看该对角线是否满足条件
static boolean udg[]=new boolean[N];//看该反对角线是否满足条件
public static void dfs(int u){
if(u==n){
for(int i=0;i<n;i++) {
for(int j=0;j<n;j++){
pw.print(g[i][j]);
}
pw.println();
}
pw.println();
return;
}
for(int i=0;i<n;i++){//u代表第几行,i代表第几列
if(!col[i] && !dg[i+u] && !udg[u-i+n]){
g[u][i]='Q';
col[i]=dg[i+u]=udg[u-i+n]=true;
dfs(u+1);
col[i]=dg[i+u]=udg[u-i+n]=false;
g[u][i]='.';
}
}
}
public static void main(String args[]) throws IOException{
n=Integer.parseInt(bf.readLine());
for(int i=0;i<n;i++){//初始化
for(int j=0;j<n;j++){
g[i][j]='.';
}
}
dfs(0);
pw.close();
}
}
二、BFS
AcWing 844. 走迷宫
思路:
利用bfs求最少次数
代码:
import java.io.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static class node{
int x,y;
public node(int x,int y){
this.x=x;
this.y=y;
}
}
static int n,m;
static int N=110;
static int g[][]=new int[N][N];
static int dist[][]=new int[N][N];
static int dx[]={0,0,1,-1};
static int dy[]={1,-1,0,0};
public static int bfs(node start){
Queue<node>queue=new LinkedList<>();
dist[start.x][start.y]=0;
queue.add(start);
while (!queue.isEmpty()){
node t=queue.poll();
for(int i=0;i<4;i++){
int x1=t.x+dx[i];
int y1=t.y+dy[i];
if(x1>=0 && x1<n && y1>=0 && y1<m && dist[x1][y1]==-1 && g[x1][y1]==0){
dist[x1][y1]=dist[t.x][t.y]+1;
queue.add(new node(x1,y1));
}
}
}
return dist[n-1][m-1];
}
public static void main(String args[]) throws IOException{
n=nextInt();
m=nextInt();
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
g[i][j]=nextInt();
}
}
node start=new node(0,0);
for(int i=0;i<n;i++) Arrays.fill(dist[i],-1);
int res=bfs(start);
pw.println(res);
pw.close();
}
}
AcWing 845. 八数码
思路:
用一个字符串string表示每个状态,则结束状态对应的字符串为12345678x,用map来存储到当前字符串对应的状态所需要的步数,利用bfs求解
一维坐标k转二维坐标:x=k/3,y=k%3 二维坐标(x,y)转一维坐标:k=x*3+y
代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
public class Main{
static BufferedReader bf=new BufferedReader(new InputStreamReader(System.in));
static PrintWriter pw=new PrintWriter(System.out);
static int dx[]={0,0,1,-1};
static int dy[]={1,-1,0,0};
static Map<String,Integer>map=new HashMap<>();
public static void swap(char c[],int x,int y){
char temp=c[x];
c[x]=c[y];
c[y]=temp;
}
public static int bfs(String start,String end){
Queue<String>queue=new LinkedList<>();
queue.add(start);
map.put(start,0);
while (!queue.isEmpty()){
String t=queue.poll();
if(t.equals(end)) return map.get(t);
int k=t.indexOf('x');
int x=k/3,y=k%3;
for(int i=0;i<4;i++){
int x1=x+dx[i];
int y1=y+dy[i];
if(x1>=0 && x1<3 && y1>=0 && y1<3){
char c[]=t.toCharArray();
swap(c,k,x1*3+y1);
String s=new String(c);
if(!map.containsKey(s)){
map.put(s,map.get(t)+1);
queue.add(s);
}
}
}
}
return -1;
}
public static void main(String args[]) throws IOException{
String s[]=bf.readLine().split(" ");
String start="";
String end="12345678x";
for(int i=0;i<s.length;i++) start+=s[i];
int res=bfs(start,end);
pw.println(res);
pw.close();
}
}
三、树与图的深度优先遍历
AcWing 846. 树的重心
思路:
res是最终结果,初始化为很大的数,dfs(u,fa)求得以u为根节点的树的点数,则删掉该点后连通块有:u的所有孩子以及整棵树-u这棵树,利用ans记录删掉该点后连通块的最大值,递归求得删除每个节点后的连通块的最大值的最小值,res即是最小的ans;
代码:
import java.io.*;
import java.util.Arrays;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static int n;
static int N=100010;
static int M=2*N;//由于是无向图,e,ne数组需要开两倍
static int e[]=new int[M];
static int ne[]=new int[M];
static int h[]=new int[N];
static int idx=0;
static int res=N;
public static void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
public static int dfs(int u,int fa){//返回以u为根的子树的总点数
int sum=1,ans=0;//sum记录该树的点数个数,至少有1个节点即是自己本身,ans记录删掉该节点后,子树点数中的最大值
for(int i=h[u];i!=-1;i=ne[i]){
int j=e[i];
if(j==fa) continue;
int s=dfs(j,u);//每个子树的点数
ans= Math.max(ans,s);//更新ans
sum+=s;//sum加上子树的点数
}
//经上述操作,ans为删除该点后子树(子树连通块)点数的最大值
ans= Math.max(ans,n-sum);//比较ans和删除该树后剩余连通块的大小
res= Math.min(res,ans);//更新res
return sum;
}
public static void main(String args[]) throws IOException{
n=nextInt();
Arrays.fill(h,-1);
for(int i=1;i<=n-1;i++){
int a=nextInt();
int b=nextInt();
add(a,b);
add(b,a);
}
dfs(1,-1);
pw.println(res);
pw.close();
}
}
四、树与图的广度优先遍历
AcWing 847. 图中点的层次
思路:
由于所有边的长度都是1,因此可利用bfs求到某一点的距离
代码:
import java.io.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static int n,m;
static int N=100010;
static int M=2*N;
static int h[]=new int[N];
static int e[]=new int[M];
static int ne[]=new int[M];
static int idx=0;
static int dist[]=new int[N];
public static void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
public static int bfs(){
Queue<Integer>queue=new LinkedList<>();
queue.add(1);
dist[1]=0;
while (!queue.isEmpty()){
int t=queue.poll();
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(dist[j]==-1){//j未被访问过
dist[j]=dist[t]+1;
queue.add(j);
}
}
}
return dist[n];
}
public static void main(String args[]) throws IOException{
n=nextInt();
m=nextInt();
Arrays.fill(h,-1);
while (m--!=0){
int a=nextInt();
int b=nextInt();
add(a,b);
}
Arrays.fill(dist,-1);
int res=bfs();
pw.println(res);
pw.close();
}
}
五、拓扑排序
AcWing 848. 有向图的拓扑序列
思路:
思路 开辟一个from数组用来存储每个点的入度,当入度为0时将该点加入拓扑序列中,最后判断拓扑序列中元素个数是否等于所有节点个数,若相等,说明存在拓扑排序,否则不存在
代码:
import java.io.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static int n,m;
static int N=100010;
static int h[]=new int[N];
static int e[]=new int[N];
static int ne[]=new int[N];
static int idx=0;
static int from[]=new int[N];
static int res[]=new int[N];
static int cnt=0;
public static void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
public static boolean topsort(){
Queue<Integer>queue=new LinkedList<>();
for(int i=1;i<=n;i++){
if(from[i]==0) queue.add(i);
}
while (!queue.isEmpty()){
int t=queue.poll();
res[cnt++]=t;
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
from[j]--;
if(from[j]==0) queue.add(j);
}
}
return cnt==n;
}
public static void main(String args[]) throws IOException{
n=nextInt();
m=nextInt();
Arrays.fill(h,-1);
while (m--!=0){
int a=nextInt();
int b=nextInt();
add(a,b);
from[b]++;
}
if(topsort()){
for(int i=0;i<cnt;i++) pw.print(res[i]+" ");
}
else pw.println(-1);
pw.close();
}
}
六、Dijkstra
AcWing 849. Dijkstra求最短路 I
说明
朴素版Dijkstra算法适用于稠密图,一般稠密图的e与n^2是一个数量级,使用邻接矩阵存储
时间复杂度O(n^2),且Dijkstra只适用于图的边权为正的情况
代码 朴素版Dijkstra:
import java.io.*;
import java.util.Arrays;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static int n,m;
static int N=510;
static int INF=0x3f3f3f3f;
static int g[][]=new int[N][N];
static int dist[]=new int[N];
static boolean flag[]=new boolean[N];
public static int dijkstra(){
Arrays.fill(dist,INF);
dist[1]=0;
for(int i=0;i<n;i++){//一共循环n次
int t=-1;
for(int j=1;j<=n;j++){//找到距离起点最近的点t
if(!flag[j] && (t==-1 || dist[j]<dist[t])) t=j;
}
flag[t]=true;//将t加入已选集合
for(int j=1;j<=n;j++) dist[j]= Math.min(dist[j],dist[t]+g[t][j]);//用t来更新其他点到起点的距离
}
return dist[n];
}
public static void main(String args[]) throws IOException{
n=nextInt();
m=nextInt();
for(int i=1;i<=n;i++){//初始化
for(int j=1;j<=n;j++){
if(i==j) g[i][j]=0;
else g[i][j]=INF;
}
}
while (m--!=0){
int a=nextInt();
int b=nextInt();
int c=nextInt();
g[a][b]= Math.min(g[a][b],c);//去除重边和自环,且重边只取最小值
}
int res=dijkstra();
if(res==INF) pw.println(-1);
else pw.println(res);
pw.close();
}
}
AcWing 850. Dijkstra求最短路 II
思路:
n,m属于同一数量级,为稀疏图,使用邻接表存储图;
考虑堆优化版的Dijkstra,时间复杂度O(mlogn)
代码:
import java.io.*;
import java.util.Arrays;
import java.util.PriorityQueue;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static class node implements Comparable<node>{
int id,distance;
public node(int id,int distance){
this.id=id;
this.distance=distance;
}
public int compareTo(node p){
return Integer.compare(distance,p.distance);
}
}
static int n,m;
static int N=150010;
static int INF=0x3f3f3f3f;
static int dist[]=new int[N];
static boolean flag[]=new boolean[N];
static int h[]=new int[N];
static int e[]=new int[N];
static int w[]=new int[N];
static int ne[]=new int[N];
static int idx=0;
public static void add(int a,int b,int c){
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
public static int dijkstra(){
Arrays.fill(dist,INF);//初始化距离
dist[1]=0;
PriorityQueue<node>heap=new PriorityQueue<>();//创建小根堆
heap.add(new node(1,0));//表示到节点1的距离为0
while (!heap.isEmpty()){
node t=heap.poll();//得到最小值
int id=t.id;
int distance=t.distance;
if(!flag[id]){//保证新加入的点不在集合S中
flag[id]=true;//将其加入S
for(int i=h[id];i!=-1;i=ne[i]){//遍历与其相连的边
int j=e[i];
if(dist[j]>distance+w[i]){//更新距离
dist[j]=distance+w[i];
heap.add(new node(j,dist[j]));
}
}
}
}
return dist[n];
}
public static void main(String args[]) throws IOException{
n=nextInt();
m=nextInt();
Arrays.fill(h,-1);
while (m--!=0){//读入图
int a=nextInt();
int b=nextInt();
int c=nextInt();
add(a,b,c);
}
int res=dijkstra();
if(res==INF) pw.println(-1);
else pw.println(res);
pw.close();
}
}
七、Bellman-ford
AcWing 853. 有边数限制的最短路
思路:
Bellman-ford算法允许图的边权为负数
若有边数限制,则只能用bellman-ford算法 k为边数限制
每次操作即是进行一次松弛操作来更新dist数组,循环k次后dist数组意义是从1开始,经过不超过k条边时,能够走到其他点的最短距离,若dist[n]数组>INF/2,则不可达
时间复杂度O(nm)
代码:
import java.io.*;
import java.util.Arrays;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static class node{
int a,b,c;
public node(int a,int b,int c){
this.a=a;
this.b=b;
this.c=c;
}
}
static int n,m,k;
static int N=510;
static int M=100010;
static int dist[]=new int[N];
static node p[]=new node[M];
static int INF=0x3f3f3f3f;
static int backup[]=new int[N];
public static void bellman_ford(){
Arrays.fill(dist,INF);
dist[1]=0;
for(int i=0;i<k;i++){//边数限制
backup=Arrays.copyOf(dist,n+1);
for(int j=0;j<m;j++){//遍历所有边
int a=p[j].a;
int b=p[j].b;
int c=p[j].c;
dist[b]= Math.min(dist[b],backup[a]+c);
}
}
if(dist[n]>INF/2) pw.println("impossible");
else pw.println(dist[n]);
}
public static void main(String args[]) throws IOException{
n=nextInt();
m=nextInt();
k=nextInt();
for(int i=0;i<m;i++){
int a=nextInt();
int b=nextInt();
int c=nextInt();
p[i]=new node(a,b,c);
}
bellman_ford();
pw.close();
}
}
八、Spfa
AcWing 851. spfa求最短路
思路:
SPFA:通常用于求含负权边的单源最短路径,以及判负权环
对Bellman_ford算法进行优化,对已经被更新的点连的所有边进行判断(只有我变小了,我连的边才有可能变小)
一般时间复杂度为O(m),最坏O(nm);
代码:
import java.io.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static int n,m;
static int N=100010;
static int INF=0x3f3f3f3f;
static int dist[]=new int[N];
static int h[]=new int[N];
static int e[]=new int[N];
static int ne[]=new int[N];
static int w[]=new int[N];
static boolean flag[]=new boolean[N];//判断该点是否在队列之中,防止队列中存储重复的点
static int idx=0;
public static void add(int a,int b,int c){
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
public static void spfa(){
Arrays.fill(dist,INF);
Queue<Integer> queue=new LinkedList<>();//存储所有dist变小的节点
queue.add(1);
dist[1]=0;
flag[1]=true;
while (!queue.isEmpty()){
int t=queue.poll();
flag[t]=false;
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(dist[j]>dist[t]+w[i]){//若能被更新,表示可以被加入队列
dist[j]=dist[t]+w[i];
if(!flag[j]){//判断该点是否在队列之中,若没有,则加入队列
flag[j]=true;
queue.add(j);
}
}
}
}
if(dist[n]==INF) pw.println("impossible");
else pw.println(dist[n]);
}
public static void main(String args[]) throws IOException{
n=nextInt();
m=nextInt();
Arrays.fill(h,-1);
while (m--!=0){
int a=nextInt();
int b=nextInt();
int c=nextInt();
add(a,b,c);
}
spfa();
pw.close();
}
}
AcWing 852. spfa判断负环
思路
dist[x]数组表示从起点到x点的最短路径的长度
cnt[x]数组,表示从起点到x点的最短路径的边数
更新dist数组时同时更新cnt数组,若cnt[x]>=n,则表示该路径一定经过了n+1个点,而一共有n个点,说明有两个点重复出现,即出现负环
注意是判断该图中是否存在负环,而不是判断从1开始的负环,因此队列初始化时需要将所有点加入!
代码:
import java.io.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static int n,m;
static int N=2010;
static int M=10010;
static int h[]=new int[N];
static int e[]=new int[M];
static int ne[]=new int[M];
static int w[]=new int[M];
static boolean flag[]=new boolean[N];
static int dist[]=new int[N];
static int cnt[]=new int[N];
static int idx=0;
public static void add(int a,int b,int c){
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
public static boolean spfa(){
Queue<Integer>queue=new LinkedList<>();
for(int i=1;i<=n;i++) {//将全部点放入队列
flag[i]=true;
queue.add(i);
}
while (!queue.isEmpty()){
int t=queue.poll();
flag[t]=false;
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(dist[j]>dist[t]+w[i]){
dist[j]=dist[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n) return true;
if(!flag[j]){
queue.add(j);
flag[j]=true;
}
}
}
}
return false;
}
public static void main(String args[]) throws IOException{
n=nextInt();
m=nextInt();
Arrays.fill(h,-1);
while (m--!=0){
int a=nextInt();
int b=nextInt();
int c=nextInt();
add(a,b,c);
}
if(spfa()) pw.println("Yes");
else pw.println("No");
pw.close();
}
}
九、Floyd
AcWing 854. Floyd求最短路
思路:
Floyd算法可以解决多源汇最短路问题,基于动态规划的思想,f(k,i,j)表示从i开始只经过1~k这些点到达j的最短距离,状态转移方程f(k,i,j)=f(k-1,i,k)+f(k-1,k,j),去掉一维后得到f(i,j)=f(i,k)+f(k,j);
时间复杂度O(n^3)
代码:
import java.io.*;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static int n,m,q;
static int N=210;
static int INF=0x3f3f3f3f;
static int d[][]=new int[N][N];
public static void floyd(){
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
d[i][j]= Math.min(d[i][j],d[i][k]+d[k][j]);
}
}
}
}
public static void main(String args[]) throws IOException{
n=nextInt();
m=nextInt();
q=nextInt();
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j) d[i][j]=0;
else d[i][j]=INF;
}
}
while (m--!=0){
int a=nextInt();
int b=nextInt();
int c=nextInt();
d[a][b]= Math.min(d[a][b],c);
}
floyd();
while (q--!=0){
int a=nextInt();
int b=nextInt();
if(d[a][b]>INF/2) pw.println("impossible");
else pw.println(d[a][b]);
}
pw.close();
}
}
十、Prim
AcWing 858. Prim算法求最小生成树
思路:
朴素版Prim算法,适用于稠密图,时间复杂度O(n^2)
堆优化版Prim算法,适用于稀疏图,但由于写法复杂,因此求稀疏图的最小生成树常使用Kruskal算法
代码:
import java.io.*;
import java.util.Arrays;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static int n,m;
static int N=510;
static int INF=0x3f3f3f3f;
static int g[][]=new int[N][N];
static boolean flag[]=new boolean[N];
static int dist[]=new int[N];//dist[x]表示x到集合的最短距离
public static int prim(){
Arrays.fill(dist,INF);//初始化
dist[1]=0;
int res=0;//最小生成树的权值
for(int i=1;i<=n;i++){
int t=-1;
for(int j=1;j<=n;j++){//找到距离集合最近的点t
if(!flag[j] && (t==-1||dist[j]<dist[t])) t=j;
}
if(dist[t]==INF) return INF;//说明不连通,不存在最小生成树
flag[t]=true;//将t加入集合
res+=dist[t];
for(int j=1;j<=n;j++) dist[j]= Math.min(dist[j],g[t][j]);//用t更新其他点到集合的距离
}
return res;
}
public static void main(String args[]) throws IOException{
n=nextInt();
m=nextInt();
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j) g[i][j]=0;
else g[i][j]=INF;
}
}
while (m--!=0){//去除重边和自环
int a=nextInt();
int b=nextInt();
int c=nextInt();
g[a][b]=g[b][a]= Math.min(g[a][b],c);
}
int res=prim();
if(res==INF) pw.println("impossible");
else pw.println(res);
pw.close();
}
}
十一、Kruskal
AcWing 859. Kruskal算法求最小生成树
思路:
Kruskal算法适用于求稀疏图的最小生成树,时间复杂度0(mlogm)
其中判断两个点是否连通可利用并查集
代码:
import java.io.*;
import java.util.Arrays;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static class node implements Comparable<node>{
int a,b,c;
public node(int a,int b,int c){
this.a=a;
this.b=b;
this.c=c;
}
public int compareTo(node p){
return Integer.compare(c,p.c);
}
}
static int n,m;
static int N=100010;
static int M=2*N;
static node e[]=new node[M];
static int p[]=new int[N];
public static int find(int x){
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
public static void main(String args[]) throws IOException {
n=nextInt();
m=nextInt();
for(int i=0;i<m;i++){//读入每条边
int a=nextInt();
int b=nextInt();
int c=nextInt();
e[i]=new node(a,b,c);
}
int res=0,cnt=0;
Arrays.sort(e,0,m);//排序
for(int i=1;i<=n;i++) p[i]=i;//初始化
for(int i=0;i<m;i++){//从小到大遍历每条边
int a=e[i].a;
int b=e[i].b;
int c=e[i].c;
if(find(a)!=find(b)){
p[find(a)]=find(b);
res+=c;//权重加入res
cnt++;//权重加入res
}
}
if(cnt==n-1) pw.println(res);//说明将所有点都加入集合
else pw.println("impossible");//说明不连通
pw.close();
}
}
十二、染色法判定二分图
AcWing 860. 染色法判定二分图
思路:
二分图:可以将图分为两部分,每一部分内部没有边相连
若一个图是二分图 等价于 图中不含奇数环,利用染色法,若不发生矛盾,则说明不含奇数环,即是二分图;
时间复杂度O(n+m)
代码:
import java.io.*;
import java.util.Arrays;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static int n,m;
static int N=100010;
static int M=2*N;
static int h[]=new int[N];
static int e[]=new int[M];
static int ne[]=new int[M];
static int idx=0;
static int color[]=new int[N];
public static void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
public static boolean dfs(int u,int c){//将点u染成c颜色,并对其所有相连的点进行染色,返回是否成功
color[u]=c;
for(int i=h[u];i!=-1;i=ne[i]){
int j=e[i];
if(color[j]==0){
if(!dfs(j,3-c)) return false;//若染色失败,说明发送冲突,返回false,3-c:1变2,2变1
}
else if(color[j]==c) return false;//两个相邻点不能同色
}
return true;
}
public static void main(String args[]) throws IOException{
n=nextInt();
m=nextInt();
Arrays.fill(h,-1);
while (m--!=0){
int a=nextInt();
int b=nextInt();
add(a,b);
add(b,a);
}
boolean flag=true;//判断染色过程中是否成功
for(int i=1;i<=n;i++){//进行染色
if(color[i]==0){//该点未被染色
if(!dfs(i,1)){
flag=false;
break;
}
}
}
if(flag) pw.println("Yes");
else pw.println("No");
pw.close();
}
}
十三、匈牙利算法
AcWing 861. 二分图的最大匹配
思路:
find(x)表示能否给x找到一个匹配,尝试了所有办法都无法找到匹配才返回false
时间复杂度O(nm),但实际运行时间远小于O(nm)
代码一 邻接矩阵写法:
import java.io.*;
import java.util.Arrays;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static int n1,n2,m;
static int N=510;
static boolean g[][]=new boolean[N][N];
static int match[]=new int[N];
static boolean flag[]=new boolean[N];
public static boolean find(int x){
for(int i=1;i<=n2;i++){
if(!flag[i] && g[x][i]){//能够访问i且i未被访问
flag[i]=true;//已访问过i
if(match[i]==0 || find(match[i])){//i还没有对象或者能为i的对象找到备胎
match[i]=x;//横刀夺爱
return true;
}
}
}
return false;
}
public static void main(String arsg[]) throws IOException{
n1=nextInt();
n2=nextInt();
m=nextInt();
while(m--!=0){
int a=nextInt();
int b=nextInt();
g[a][b]=true;
}
int res=0;
for(int i=1;i<=n1;i++){
Arrays.fill(flag,false);
if(find(i)) res++;
}
pw.println(res);
pw.close();
}
}
代码二:邻接表写法
import java.io.*;
import java.util.Arrays;
public class Main {
static StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter pw=new PrintWriter(System.out);
public static int nextInt() throws IOException{
st.nextToken();
return (int)st.nval;
}
static int n1,n2,m;
static int N=510;
static int M=100010;
static int h[]=new int[N];
static int e[]=new int[M];
static int ne[]=new int[M];
static int idx=0;
static boolean flag[]=new boolean[N];
static int match[]=new int[N];
public static void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
public static boolean find(int x){//判断x是否能找到匹配的妹子
for(int i=h[x];i!=-1;i=ne[i]){
int j=e[i];
if(!flag[j]){
flag[j]=true;
if(match[j]==0 || find(match[j])){//妹子未被匹配 或 已经被匹配,但可给对应男生找下家
match[j]=x;
return true;
}
}
}
return false;
}
public static void main(String args[]) throws IOException{
n1=nextInt();
n2=nextInt();
m=nextInt();
Arrays.fill(h,-1);;
while (m--!=0){
int a=nextInt();
int b=nextInt();
add(a,b);
}
int res=0;
for(int i=1;i<=n1;i++){//遍历左半部分
Arrays.fill(flag,false);
if(find(i)) res++;
}
pw.println(res);
pw.close();
}
}