グラフ理論に関するトピック (さまざまなアルゴリズムと Blue Bridge Cup の実際のテスト問題)

1. グラフ理論の概要

1.1 サイド入金方法

1.1.1 アレイエッジストレージ

 1.1.2 隣接マトリックスエッジストレージ

 1.1.3 隣接するテーブルストレージエッジ

 1.2 グラフの走査と接続性

DFSおよびBFSを介して各グラフを走査する

接続されていないグラフの場合、ループは各点dfsで動作します。

ユニオン検索でも接続性を判定可能

1.2.1 地球温暖化の例

import sys
sys.setrecursionlimit(60000)  # 设置最大递归深度,默认递归深度有点小,不设置递归会出问题
 
def dfs(x,y):
  d=[(-1,0),(0,1),(1,0),(0,-1)]  # 左 上 右 下
  global flag
  vis[x][y] =1
  if mp[x][y+1]=='#' and mp[x][y-1] =='#' and mp[x+1][y]=='#' and mp[x-1][y]=='#':
    # 说明点(x,y)四周都是陆地
    flag = 1  # 高地标记,不会被淹没
  for i in range(4):
    nx=x+d[i][0]
    ny=y+d[i][1]
    if vis[nx][ny] ==0 and mp[nx][ny] =='#':
      # 如果当前没有遍历点(nx,ny)同时地图上面该点不是陆地
      dfs(nx,ny)
 
n=int(input())
mp = []   # 记录地图
for i in range(n):
  mp.append(list(input()))
vis =[]   # 判断是否走过
for i in range(n):
  vis.append([0]*n)
 
ans =0
for i in range(n):  # 遍历每一点
  for j in range(n):
    if vis[i][j] ==0 and mp[i][j] =='#':  # 相当于找连通分量
      flag = 0
      dfs(i,j)
      if flag==0:
        ans+=1
 
print(ans)
    

1.3 オイラー道路とオイラーサーキット

 ハミルトニアン回路: グラフ内のすべての点が 1 回だけ通過します

1.3.1 オイラーロードとオイラーサーキットの判定

        無向グラフ

        グラフ内の点がすべて偶数点の場合、オイラー回路が存在し、任意の点を始点と終点として使用できます。

        特異点が 2 つだけあり、そのうちの 1 つが開始点で、もう 1 つが終了点である場合、オイラー ロードが存在します。奇数の特異点を持つ無向グラフを作成することは不可能です。

        有向グラフ

        点の出次数は 1 として記録され、入次数は -1 として記録されます。この点のすべての出次数と入次数の合計がその次数になります。
        有向グラフは、グラフ内のすべての頂点の次数が 0 である場合に限り、オイラー回路を持ちます。
        次数 1 の点が 1 つだけ、次数 -1 の点が 1 つだけ、および次数 0 の他のすべての点がある場合、次数 1 の点が始点となり、次数 -1 の点が始点となるオイラー パスが存在します。終点。

1.3.2 オイラーロードキングの例

## 不全,没看懂
import sys
import collections
import itertools
import heapq
sys.setrecursionlimit(300000)

def dfs(u):
    i=d[u]   # 从点u的第一条边i=0开始
    while(i<len(G[u])):
        d[u]=i+1  # 后面继续走u的下一条边
        dfs(G[u][i]) # 继续走这条边的邻居点
        i=dp[u]   # 第i条边走过了,不再重复走
    rec.append(u)

M=100100
n,m = map(int,input().split())  # n个点,m条边
du=[[0 for _ in range(2)] for _ in range(M)]  # 记录入度,出度
G=[[] for _ in range(n+1)]  # 临接表存图
d=[0 for _ in range(M)]  # d[u]=i : 当前走u的第i个边
rec=[]   #记录欧拉路
    

for i in range(m):
    u,v =map(int,input().split())
    G[u].append(v)
    du[u][1]+=1   #出度
    du[v][0]+=1   #入度
for i in range(1,n+1):
    G[i].sort()   # 对邻居点排序,字典序
    S=1

2. フロイドアルゴリズム

2.1 フロイドの紹介

 アルゴリズムの比較

 2.2 アルゴリズムテンプレート

import sys
import collections
import itertools
import heapq
sys.setrecursionlimit(300000)

def floyd():
    for k in range(1,n+1):
        for i in range(1,n+1):
            for j in range(1,n+1):
                if dp[i][k]+dp[k][j] <dp[i][j]:
                    dp[i][j]=dp[i][k]+dp[k][j] 

 2.3 アルゴリズムの概要

 2.4 アルゴリズムの例

2.4.1 ブルーブリッジパーク

import sys
import collections
import itertools
import heapq
sys.setrecursionlimit(300000)



def floyd():
  for i in range(1,n+1):
    for j in range(1,n+1):
      for k in range(1,n+1):
        dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j])
 
n,m,q = map(int,input().split())
inf=2**120  #自定义无穷大
dp=[[inf]*(n+1) for i in range(n+1)]  # 初始为无穷大
choice=[]
for i in range(m):
  u,v,w=map(int,input().split())   # 无向图,临接矩阵存边
  dp[u][v]=w
  dp[v][u]=w 
for i in range(q):   # 读 起点和终点
  s,d = map(int,input().split()) 
  choice.append((s,d))
floyd()
for s,d in choice:
  if dp[s][d]!=inf:
    print(dp[s][d])
    continue
  print(-1)
   

2.4.2 パス

 標準的なフロイドアルゴリズム

import sys
import collections
import itertools
import heapq
import math
sys.setrecursionlimit(300000)


# 标准的floyd


def lcm(x,y):   # 求最下公倍数
    return x//math.gcd(x,y)*y

def floyd():
    for k in range(1,2022):
        for i in range(1,2022):
            for j in range(1,2022):
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j])

        
dp=[[int(0x3f3f3f3f3f3f3f) for _ in range(2022)] for _ in range(2022)]
for i in range(1,2022):
    for j in range(1,2022):
        if abs(i-j)<=21:  # 题意中的如果两个结点的差的绝对值不大于21
            dp[i][j]=lcm(i,j)

print(dp[1][2021]

 フロイドアルゴリズムの簡易版

import sys
import collections
import itertools
import heapq
sys.setrecursionlimit(300000)


import math
def lcm(i,j):
  return i//math.gcd(i,j)*j
 
dp=[[2**50]*2022 for i in range(2022)]
# 创建图
for i in range(1,2022):
  for j in range(i,2022):  
    if abs(i-j)<=21:
      dp[i][j]=lcm(i,j)
 
# 找最短路径
for k in range(1,2022):
  for i in range(1,2):
    for j in range(1,2022):
      dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j])
print(dp[1][2021])  # 1026837
 

ベルマン・フォードアルゴリズム

import sys
import collections
import itertools
import heapq
import math
sys.setrecursionlimit(300000)


# Bellman_Ford算法


def lcm(x,y):   # 求最下公倍数
    return x//math.gcd(x,y)*y
        
dp=[int(0x3f3f3f3f3f3f3f) for _ in range(2022)]
for i in range(1,2022):
    for j in range(i+1,i+22): # 题意中的如果两个结点的差的绝对值不大于21
        if j>2021: break
            dp[j]=min(dp[j],dp[i]+lcm(i,j))  # 更新最短路

print(dp[2021])

3. ディジストラアルゴリズム

3.1 アルゴリズムの概要

 

 3.2 アルゴリズムの例

 

 

 3.3 テンプレートの例

3.3.1 ブルーブリッジ王国

import sys
import collections
import itertools
import heapq   # 默认小顶堆
import math
sys.setrecursionlimit(300000)


def dij(s):
    vis=[0 for i in range(n+1)]  # 标志是否访问过
    hp=[]  # 堆
    dis[s]=0  # 自身到自身的距离为0
    heapq.heappush(hp,(0,s))  # 列表堆化同时入堆
    while hp:
        u=heapq.heappop(hp)[1]  # 出堆,出的是结点
        if vis[u]: # 判断是否处理过
            continue
        vis[u]=1
        for i in G[u]:
            v,w=i[0],i[1]
            if vis[v]:
                continue
            if dis[v]>dis[u]+w:
                dis[v]=dis[u]+w
                heapq.heappush(hp,(dis[v],v))
    

n,m=map(int,input().split())
s=1
G=[[]for i in range(n+1)]  # 临接表存图
inf=2**64
dis=[inf]*(n+1)   # 从1到其他点的距离
for i in range(m):  # 邻接表存m条边
    u,v,w=map(int,input().split())
    G[u].append((v,w))
dij(s)   # 以s为起点到其他点的最短路径
for i in range(1,n+1):
    if dis[i]>=inf :
        print("-1",end=' ')
    else:
        print(dis[i],end=" ")
    

3.3.2 行列-行列形式のダイストラ テンプレート

 

import sys  #设置递归深度
import collections  #队列
import itertools  # 排列组合
import heapq  #小顶堆
import math
sys.setrecursionlimit(300000)
 
 
 
def dij():
   dist[1]=0  #很重要
   for _ in range(n-1): # 还有n-1个点没有遍历
      t=-1
      for j in range(1,n+1):
         if st[j]==0 and (t==-1 or dist[t]>dist[j]):  #找到没处理过得最小距离点
            t=j
      for j in range(1,n+1):
         dist[j]=min(dist[j],dist[t]+gra[t][j])  # t-j的距离,找最小值
      st[t]=1  # 标记处理过
   return dist[n]
      
n,m=map(int,input().split())
 #下标全部转为从1开始
stay=[0]+list(map(int,input().split()))
stay[n]=0   
gra = [[float('inf')] * (n+1) for _ in range(n+1)]
dist = [float('inf')] * (n+1)
st=[0]*(n+1)  # 标志是否处理
 
 
for i in range(m):
   u,v,w=map(int,input().split()) #这里重构图
   gra[u][v]=stay[v]+w
   gra[v][u]=stay[u]+w
 
 
print(dij())
   

4. ベルマン・フォードアルゴリズム

4.1 アルゴリズムの概要

単一ソース最短経路問題: 開始点 s が与えられると、そこからグラフ内の n 個のノードすべてまでの最短経路を見つけます。

 4.2 アルゴリズムのテンプレートと例

4.2.1 出張の問題

 ベルマン - フォードの実装

import sys
import collections
import itertools
import heapq   # 默认小顶堆
import math
sys.setrecursionlimit(300000)

n,m = map(int,input().split())
t=[0]+list(map(int,input().split()))  # 从t=1开始
e=[]  # 数组存边
for i in range(1,m+1):
    a,b,c = map(int,input().split())
    e.append([a,b,c])
    e.append([b,a,c])    # 双向边

dist=[2**64]*(n+1)  # 存储到终点的距离
dist[1]=0
for k in range(1,n+1):  # 最大循环n次,即n个点
    for a,b,c in e:  # 检查每条边
        res=t[b]  # b的隔离时间
        if b==n:
            res=0
        dist[b]=min(dist[b],dist[a]+c+res)   # 问邻居是否能到达起点
print(dist[n])

 ディジストラの実装

import sys  #设置递归深度
import collections  #队列
import itertools  # 排列组合
import heapq  #小顶堆
import math
sys.setrecursionlimit(300000)
 
 
 
def dij():
   dist[1]=0  #很重要
   for _ in range(n-1): # 还有n-1个点没有遍历
      t=-1
      for j in range(1,n+1):
         if st[j]==0 and (t==-1 or dist[t]>dist[j]):  #找到没处理过得最小距离点
            t=j
      for j in range(1,n+1):
         dist[j]=min(dist[j],dist[t]+gra[t][j])  # t-j的距离,找最小值
      st[t]=1  # 标记处理过
   return dist[n]
      
n,m=map(int,input().split())
 #下标全部转为从1开始
stay=[0]+list(map(int,input().split()))
stay[n]=0   
gra = [[float('inf')] * (n+1) for _ in range(n+1)]
dist = [float('inf')] * (n+1)
st=[0]*(n+1)  # 标志是否处理
 
 
for i in range(m):
   u,v,w=map(int,input().split()) #这里重构图
   gra[u][v]=stay[v]+w
   gra[v][u]=stay[u]+w
 
 
print(dij())
   

  

5. SPFAアルゴリズム

5.1 アルゴリズムの概要

改良された Bellman-Ford アルゴリズム

 5.2 アルゴリズムのステップ

5.3 アルゴリズムの例とテンプレート

5.3.1 ランダムデータ下の最短経路問題

import sys
import collections
import itertools
import heapq   # 默认小顶堆
import math
sys.setrecursionlimit(300000)


def spfa(s):
    dis[s]=0
    hp=[]
    heapq.heappush(hp,s)
    inq=[0]*(n+1)   # 判断是否在队列中
    inq[s]=1
    while(hp):
        u=heapq.heappop(hp)
        inq[u]=0
        ''' 下面两句认为没用,因为队列中的u都是因为上一个结点的邻居更新后
            放进来的,删了之后一样AC '''
        if dis[u]==inf:   #到起点的距离为无穷大,没有必要更新邻居
            continue
        for v,w in e[u]:    # 遍历u的邻接表
            if dis[v]>dis[u]+w:
                dis[v]=dis[u]+w
                if(inq[v]==0):  # 状态有更新,v的邻居可以通过他得到更近路径
                    heapq.heappush(hp,v)
                    inq[v]=1
n,m,s = map(int,input().split())
e=[[] for i in range(n+1)]  # 临接表存边
inf=2**64
dis=[inf]*(n+1)
for i in range(m):  # 读边
    u,v,w = map(int,input().split())
    e[u].append((v,w))   # 邻接表存边,有向图
spfa(s)
for i in range(1,n+1):
    if dis[i]>=inf:
        print('-1',end=' ')
    else:
        print(dis[i],end=' ')

 5.4 ダイジストラとSPFAの比較

6. 最小スパニングツリーアルゴリズム

6.1 プリムのアルゴリズム

6.1.1 道路の建設(テンプレート例)

import sys
import collections
import itertools
import heapq   # 默认小顶堆
import math
sys.setrecursionlimit(300000)


def prim():
    ans,cnt=0,0   # cnt 是加入MST的点的数量
    q=[]
    vis=[0 for i in range(n+1)]   # 1 表示点在MST中
    heapq.heappush(q,(0,1))
    while q and cnt<n:
        w,u = heapq.heappop(q)   # 出距离集合最近的点
        if  vis[u] !=1:  # 不再MST中
            vis[u]=1
            ans+=w
            cnt+=1
            for v,w in e[u]:   # 遍历点u的邻居,边长为w
                heapq.heappush(q,[w,v])  # 加入MST的点的数量不等于n,说明原图不连通
    if cnt!=n:   # 加入MST的点的数量不等于n,说明原图不连通
        print('-1')
    else:
        print(ans)

        
n,m = map(int,input().split())
e=[[] for i in range(n+1)]
for i in range(m):
    u,v,w  =map(int,ipnut().split())
    e[u].append((v,w))  # u的邻居是v,边长w
    e[v].append((u,w))  # 双向边

prim()

6.2 クルーサルアルゴリズム

ユニオンチェックにより実現

 6.2.1 テンプレート例(道路工事)

import sys
import collections
import itertools
import heapq   # 默认小顶堆
import math
sys.setrecursionlimit(300000)


def find(x):
    if s[x] ==x:
        return x
    s[x]=find(s[x])  # 路径压缩
    return s[x]

def merge(x,y):
     s[find(y)]=find(x)
def kruskal():
    cnt=0
    ans=0
    e.sort(key=lambda x: x[2])  # 将边从小到大排序
    for i in range(n+1):   # 并查集初始化
       s.append(i)
    for i in range(m):  # 遍历所有边
        x,y=e[i][0],e[i][1]
        e1,e2 =find(x),find(y)
        if e1==e2:  # 属于同一个集,要这条边的话产生圈
            continue
        else:
            ans+=e[i][2]
            merge(x,y)
            cnt+=1
        if cnt==n-1:
            break
    if cnt!=n-1:   # 边的数量不等于n-1,说明有点不再MST上面
        print(-1)
    else:
        print(ans)
    return
e=[]  # 数组存边
s=[]  # 并查集
n,m=map(int,input().split())
for i in range(m): # 存m条边
    u,v,w = map(int,input().split())
    e.append((u,v,w))   # 存边
kruskal()

6.3 2 つのアルゴリズムの比較

7. 国内競争問題に対する 4 つの解決策

import os
import sys
import itertools
import heapq

# Bellman-Ford O(mn)
'''
n,m=map(int,input().split())
stay=[0]+list(map(int,input().split()))
stay[n]=0  # 终点不需要隔离
e=[]
for i in range(m):
  a,b,c=map(int,input().split())
  e.append([a,b,c])
  e.append([b,a,c])

dp=[sys.maxsize for i in range(n+1)] 
dp[1]=0  #到起点的距离为0
def Bellman_ford():
  for i in range(1,n+1): # n个点
    for j in e:  # m条边
      v1,v2,cost=j
      #if v2==n:  # n号城市不需要隔离时间
      dp[v2]=min(dp[v2],dp[v1]+cost+stay[v2])
Bellman_ford()
print(dp[n])
'''


# Dijstra  临接表写法 O(n*n)
'''
n,m=map(int,input().split())
stay=[0]+list(map(int,input().split()))
stay[n]=0  # 终点不需要隔离
edge=[[]for i in range(n+1)]
for i in range(m):
  a,b,c=map(int,input().split())
  edge[a].append([b,c+stay[b]])
  edge[b].append([a,c+stay[a]])

hp=[]
vis=[0 for i in range(n+1)]
dp=[sys.maxsize for i in range(n+1)]
dp[1]=0  # 自身到自身距离为0
def dijstra():
  heapq.heappush(hp,(0,1)) 
  while hp:
    u=heapq.heappop(hp)[1]
    if vis[u]:
      continue
    vis[u]=1
    for i in range(len(edge[u])): # 遍历u的边
      v,w=edge[u][i][0],edge[u][i][1]
      if vis[v]:
        continue
      if dp[v]>dp[u]+w:
        dp[v]=dp[u]+w
        heapq.heappush(hp,(dp[v],v))    
dijstra()

print(dp[n])
'''


# Dijstra临接矩阵写法  O(n*n)
'''
n,m=map(int,input().split())
stay=[0]+list(map(int,input().split()))
stay[n]=0  # 终点不需要隔离
edge=[[sys.maxsize for i in range(n+1)]for i in range(n+1)]
for i in range(m):
  a,b,c=map(int,input().split())
  edge[a][b]=c+stay[b]
  edge[b][a]=c+stay[a]

vis=[0 for i in range(n+1)]
dp=[sys.maxsize for i in range(n+1)]
dp[1]=0  # 自身到自身距离为0

def dijstra():
  for i in range(n-1):   #只需要处理n-1个点
    t=-1
    for j in range(1,n+1):   # 找到没处理过得并且距离最小的
      if vis[j]==0 and(t==-1 or dp[j]<dp[t] ):
        t=j
    for j in range(1,n+1):
      dp[j]=min(dp[j],dp[t]+edge[t][j])
    vis[t]=1
dijstra()
print(dp[n])
'''

# SPFA
'''
n,m=map(int,input().split())
stay=[0]+list(map(int,input().split()))
stay[n]=0  # 终点不需要隔离
edge=[[]for i in range(n+1)]   #临接表存边
for i in range(m):
  a,b,c=map(int,input().split())
  edge[a].append([b,c+stay[b]])
  edge[b].append([a,c+stay[a]])
  
dp=[sys.maxsize for i in range(n+1)]
dp[1]=0  # 自身到自身距离为0


def spfa():
  hp=[]
  heapq.heappush(hp,1)   #用堆实现相当于优先队列,加快一点效率罢了
  in_hp=[0 for i in range(n+1)]  # 标记数组换为了判断是否在队列中 
  in_hp[1]=1  # 1在队列
  while hp:
    u=heapq.heappop(hp)
    in_hp[u]=0  # 标记为不在队列
    if dp[u]==sys.maxsize:   # 没有处理过的点,直接跳过,只处理邻居点
      continue
    for i in range(len(edge[u])): # 遍历u的边
      v,w=edge[u][i][0],edge[u][i][1]
      if dp[v]>dp[u]+w:
        dp[v]=dp[u]+w
        if in_hp[v]==0: # 他的邻居不再队列,就把他入队,方便下下次更新邻居的邻居结点
          heapq.heappush(hp,v)    
          in_hp[v]=1
spfa()
print(dp[n])
'''

おすすめ

転載: blog.csdn.net/weixin_52261094/article/details/130477269