算法竞赛入门经典第6章:图论基础

6.12 用DFS求连通块。

这种算法也叫种子填充,每一次找出一个连通块,直到不存在连通块为止。利用DFS进行8个方向的遍历。

def proc(graph):
    visited = []
    buf = []
    cnt = 0
    def inbound(x,y):return x<m and y<n and x>-1 and y>-1
    def init(m,n):
        nonlocal visited,buf,cnt
        cnt = 0
        visited,buf = [[None]*n for i in range(m)],[[-1]*n for i in range(m)]

    m,n,flag = 1,8,False
    init(m,n)
    def floodfill(x,y,k):
        nonlocal cnt,buf,visited,graph,flag
        if visited[x][y]!=None or graph[x][y]!='@':return
        visited[x][y] = 1
        if buf[x][y] ==-1:buf[x][y] = k;flag = True
        else:return
        for i in range(-1,2):
            for j in range(-1,2):
                u,v = x+i,y+j
                if inbound(u,v):floodfill(u,v,k)
    pq = []
    for i,each in enumerate(graph):
        for j,node in enumerate(each):
            if node=='@':pq.append((i,j))
    for k,item in enumerate(pq):
        floodfill(item[0],item[1],k)
        if flag==True:cnt+=1
        flag = False
    print(cnt)
proc([list('@@**@*@*')])

6.13 个人认为这个题目还是有一定难度的,主要思路有两点:

a :利用floodfill算法先将所有共享白色部分遍历一遍。

b:寻找黑色连通块,对找到的每一个黑色连通块还要在里面找白色连通块,由于已经排除外部白色块的干扰,所以找到的白色连通块的数量就可以用来标记该符号到底是哪一种类型。

c :最后,在寻找黑色连通块函数里面处理白色连通块的时候一定要注意,不要提前将访问标志标志成访问,这样你就会直接退出白色连通块的函数,应该由白色连通块的函数来标记。只有黑色点才由黑色函数标记。这个细节让我搞了半小时才找到。

最后输入输出的格式我也没有处理了。。。。

To = {'0':'0000','1':'0001','2':'0010','3':'0011','4':'0100','5':'0101','6':'0110','7':'0111',\
'8':'1000','9':'1001','a':'1010','b':'1011','c':'1100','d':'1101','e':'1110','f':'1111'}
From = {1:'A',3:'J',5:'D',4:'S',0:'W',2:'K'}
def proc():
    def inbound(x,y):
        nonlocal h,w
        return x>-1 and y>-1 and x<h and y<w*4
    def white_search(x,y):
        nonlocal graph,visited
        if visited[x][y]==1 or graph[x][y]=='1':return
        visited[x][y]=1
        for i in range(-1,2):
            for j in range(-1,2):
                if i*j==0 and inbound(x+i,y+j):white_search(x+i,y+j)
    def black_search(x,y):
        nonlocal white_cnt,graph,visited
        if visited[x][y]==1:return
        if graph[x][y]=='0':
            white_cnt+=1
            white_search(x,y)
            return
        else:visited[x][y]=1
        for i in range(-1,2):
            for j in range(-1,2):
                if i*j==0 and inbound(x+i,y+j):black_search(x+i,y+j)
    def first(graph):
        return [0,0]
    line = input()
    line = line.split(' ')
    h,w = int(line[0]),int(line[1])
    count = h
    visited = [[0]*w*4 for i in range(h)]
    graph = []
    while count:
        count-=1
        line = input()
        codes = reduce(lambda x,y:x+y,[To[ch] for ch in line],'')
        graph.append(codes)
    black_cnt = 0
    white_search(*first(graph))
    for i in range(len(graph)):
        for j in range(len(line)):
            if visited[i][j]==0 and graph[i][j]=='1':
                white_cnt = 0
                black_cnt+=1;black_search(i,j)
                print(From[white_cnt])
proc()

30 10
0000000000
0ffffffff0
0fff00fff0
0fff00fff0
0fff00fff0
0ffffffff0
000fff0000
000fff0000
000fff0000
00fffff000
0000000000
0ffffffff0
0f0f00f0f0
0f0f00f0f0
0fff00f0f0
0ffffffff0
000fff0000
000fff0000
000fff0000
00fffff000
0000000000
0ffffff000
0ffffff000
0ffffff000
0ffffff000
0ffffff000
000ff00000
000ff00f00
000fffff00
0000000000

614 BFS找最短路,迷宫问题。此题不难,关键是将状态写成一个三元组,并且做好各个状态之间的转换。同时要注意初始状态是走了一步之后的状态。


final_state = None
go = {0:(-1,0),1:(0,-1),2:(1,0),3:(0,1)}
D = {'L':1,'R':-1,'F':0}
def BFS(q,goal,states,maze):
    global final_state,go
    while len(q):
        x,y,r = q.popleft()
        if (x,y) == goal:final_state = (x,y,r);break
        rule = maze[x][y]
        possible_ways = analyze(r,rule)
        for next_way in possible_ways:
            i,j = go[next_way]#according to turn,determine the next direction
            next_state = (x+i,y+j,next_way)
            if next_state not in states:
                states[next_state] = (x,y,r)
                q.append(next_state)
def analyze(r,rule):
    global D
    t = {0:'N',1:'W',2:'S',3:'E'}
    u = {'N':0,'W':1,'S':2,'E':3}
    a = [[(r+D[d])%4 for d in each[1:]]for each in rule if u[each[0]]==r]
    return a[0] if a!=[] else []
def process_data(data):
    ans = []
    data = data.split(' ')
    x,y = int(data[0]),int(data[1])
    for rule in data[2:-1]:
        ans.append(rule)
    return x,y,ans

def track(final_state,states):
    if final_state in states:
        p = states[final_state]
        track(p,states)
    print(final_state,end='->')
def proc():
    global go
    t = {'N':0,'W':1,'S':2,'E':3}
    states = {}

    while 1:
        name = input()
        if name=='END':break
        maze = [[[] for j in range(9)] for i in range(9)]
        f = input()
        f = f.split(' ')
        w,goal = (int(f[0]),int(f[1]),t[f[2]]),(int(f[3]),int(f[4]))
        i,j= go[w[2]]
        initial = (w[0]+i,w[1]+j,w[2])
        print(states)
        while 1:
            data = input()
            if data=='0':break
            x,y,rule = process_data(data)
            maze[x][y] = rule
        q = deque()
        q.append(initial)
        states = {}
        final_state = None
        states[initial] = w
        BFS(q,goal,states,maze)
        track(final_state,states)
proc()

6.15 拓扑排序

这里提供了两种拓扑排序的方法,第一种方法的复杂度是 O(E+V) ,第二种方法采用DFS,复杂度也是 O(E+V) ,但是方法1不需要递归。

(1) :采用以前学过的算法,将入度为0的点放入一个集合,每次取出来一个。入度为0就代表所有在它前面的点都已经被输出了。然后不停的更新所有的节点的入度。

def topsort(graph_dic,n):
    Indegree = [0]*(n+1)
    sort_ans = []
    box = []
    for key,v in graph_dic.items():
        for vertex in v:Indegree[vertex]+=1
    for i,each in enumerate(Indegree[1:]):
        if each==0:box.append(i+1)
    while len(box):
        first = box.pop()
        sort_ans.append(first)
        if first not in graph_dic:continue
        for adj_v in graph_dic[first]:
            Indegree[adj_v]-=1
            if Indegree[adj_v]==0:
                box.append(adj_v)
    assert(len(sort_ans)==n)
    return sort_ans

def proc():
    def get_2():
        first_line = input()
        first_line = first_line.split(' ')
        return  int(first_line[0]),int(first_line[1])
    while 1:
        n,m = get_2()
        if not m|n:break
        t = m
        graph_dic = {}
        while t:
            a,b = get_2()
            if a in graph_dic:graph_dic[a].append(b)
            else:graph_dic[a]=[b]
            t-=1
        print(topsort(graph_dic,n))

(2) :书上用dfs给出解答,关键是注意用-1,0,1来标记节点的不同状态!只有当某一个节点visited[v]==-1的时候,我们才能确定这里有一个有向环。仔细想一想,这说明我们正处于某个递归的访问之中,但是访问到了前面的节点。这必然是存在有向环。

def dfs(graph_dic,visited,sort_ans,u):
    visited[u] = -1
    for v in (graph_dic[u] if u in graph_dic else []):
        print(u,v,visited)
        if visited[v]<0:print('aaaaaa',u,v,visited);return False
        elif not visited[v] and not dfs(graph_dic,visited,sort_ans,v):return False
    visited[u] = 1
    sort_ans.appendleft(u)
    return True
def procdfs():
    def get_2():
        first_line = input()
        first_line = first_line.split(' ')
        return  int(first_line[0]),int(first_line[1])
    while 1:
        n,m = get_2()
        if not m|n:break
        t = m
        graph_dic = {}
        while t:
            a,b = get_2()
            if a in graph_dic:graph_dic[a].append(b)
            else:graph_dic[a]=[b]
            t-=1
        sort_ans,visited = deque(),[0]*(n+1)
        for u in range(1,n+1):
            if not visited[u]:dfs(graph_dic,visited,sort_ans,u)
        print(sort_ans)
procdfs()

补充 (3) :求出所有的拓扑排序:如果有这样的要求,似乎不能够使用dfs直接求解了。不信可以自己编程尝试一下。我采取的方法是在(1)的基础上进行一个全排列的思路。对每一个可选的入度为0的点都加入一次,然后递归的处理问题的子集。注意需要将最初始的情况在topsort外部计算好。

    def topsort(graph_dic,sort_ans,Indegree,box,all_result,n):

        if len(sort_ans)==n:all_result.append(sort_ans);return
        for first in box:
            next_sort_ans,next_Indegree,next_box = sort_ans[::],Indegree[::],box[::]
            next_box.remove(first)
            next_sort_ans.append(first)
            #update the adj vertex
            if first in graph_dic:
                for adj_v in graph_dic[first]:
                    next_Indegree[adj_v]-=1
                    if next_Indegree[adj_v]==0:
                        next_box.append(adj_v)
            topsort(graph_dic,next_sort_ans,next_Indegree,next_box,all_result,n)

def proc():
    def get_2():
        first_line = input()
        first_line = first_line.split(' ')
        return  int(first_line[0]),int(first_line[1])
    while 1:
        n,m = get_2()
        if not m|n:break
        t = m
        graph_dic = {}
        while t:
            a,b = get_2()
            if a in graph_dic:graph_dic[a].append(b)
            else:graph_dic[a]=[b]
            t-=1
        Indegree,box,all_res = [0]*(n+1),[],[]
        for key,v in graph_dic.items():
            for vertex in v:Indegree[vertex]+=1
        for i,each in enumerate(Indegree[1:]):
            if each==0:box.append(i+1)
        topsort(graph_dic,[],Indegree,box,all_res,n)
    print(all_res)
proc()

616 :

首先给出一个我自己写的,可以找出欧拉回路的程序:

代码比较多,基本思路是判断连通(构造有向图,有向图-〉无向图->dfs判断连通,统计度数)



def is_con(g,vertexs):
    ind = {}
    for v in vertexs:ind[v] = 0
    for u in g:
        for v in g[u]:ind[v]+=1;ind[u]+=1
    t = [0 for v in vertexs if ind[v]==0]
    return len(t)==0
def double_graph(g):
    base_graph = g.copy()
    for each in g:
        for v in base_graph[each]:
            if v in base_graph:
                if each not in base_graph[v]:base_graph[v].append(each)
            else:
                base_graph[v] = [each]
    return base_graph
def euler_circle(graph,base_graph,vertexs):
    def select(vertexs):
        nonlocal graph,first
        ind = {};outd = {}
        for v in vertexs:ind[v] = 0;outd[v]=0
        for vertex in graph:
            for each in graph[vertex]:
                outd[vertex]+=1
                ind[each[0]]+=1
        t = [(v,ind[v]-outd[v]) for v in vertexs if ind[v]-outd[v]!=0]
        if len(t)==0:return True
        elif len(t)!=2:return False
        else:
            a,b = t[0],t[1]
            if a[1]*b[1]!=-1:return False
            first =  a if a[1]==-1 else b
            return True
    def euler(u):
        nonlocal graph,vis
        for e in graph[u] if u in graph else []:
            if e not in vis:
                vis.add(e)
                euler(e[0])
                print(edges[e[1]])
    vis = set()
    first = None
    if select(vertexs) and is_con(double_graph(base_graph),vertexs):
        euler(first[0])
    else:print('No')
edges = {}
def proc():
    global edges
    T = int(input())
    m = int(input())
    graph,vs = {},set()
    base_graph = {}
    cnt = 0
    while m:
        word = input()
        m-=1;cnt+=1
        edges[cnt] = word
        vs.add(word[0]);vs.add(word[-1])
        if word[0] in graph:
            graph[word[0]].append((word[-1],cnt))
            if word[-1] not in base_graph[word[0]]:base_graph[word[0]].append(word[-1])
        else:
            graph[word[0]] = [(word[-1],cnt)]
            base_graph[word[0]] = [word[-1]]
    print(edges)
    euler_circle(graph,base_graph,vs)
proc()

接着是参考答案代码的精简版,不得不说确实很简洁的代码。代码减少了几乎一小半。

(a) 利用了一个以前学习过的结构:并查集来判断连通性,这个真的很简洁。但是第一次遇到还是觉得有一定技巧性。注意到,每合并两个集合,就代表连通块的数量减少1,最后只需要判断到底有多少连通块就好。
(b) 将边进行编号,因为可能会有重复的边出现,但是也是不同的边。
(c) 在构造图的时候就统计度数,而不是放在函数内部去统计,一次循环就搞定。这样虽然增加代码耦合性,但是更加简洁。对于这种做题的代码比较实用。
edges = {}
pa = list(range(256))
def findset(x):return x if pa[x]==x else findset(pa[x])

def proc():
    global edges
    T = int(input())
    m = int(input())
    graph,vs,deg = {},set(),[0]*27
    cnt,con = 0,0
    while m:
        word = input()
        m-=1;cnt+=1
        edges[cnt] = word
        c1,c2 = ord(word[0])-ord('a'),ord(word[-1])-ord('a')
        vs.add(c1);vs.add(c2)
        if c1 in graph:graph[c1].append((c2,cnt))
        else:graph[c1] = [(c2,cnt)]
        s1,s2 = findset(c1),findset(c2)
        if s1!=s2:pa[s1] = s2;con+=1
        deg[c1]-=1;deg[c2]+=1

    con = len(vs)-con;
    t = [i for i in deg if i!=0]
    if con==1 and (len(t)==0 or (len(t)==2 and t[0]*t[1]==-1)): 
        first = deg.index(-1)
        euler_circle(graph,vs,first)
    else:print('No Possible road')

def euler_circle(graph,vertexs,u):
    def euler(u):
        nonlocal graph,vis
        for e in graph[u] if u in graph else []:
            if e not in vis:
                vis.add(e)
                euler(e[0])
                print(edges[e[1]])
    vis = set()
    first = None
    euler(u)
proc()
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值