graph 기초개념 및 구현실습

2019-05-10

.

그림, 실습코드 등 학습자료 출처 : https://github.com/ythwork

1. 그래프 개요

  • 그래프 G는 두 집합 V,E로 나타낸다.

1) V(G) : 노드 혹은 정점(vertex)의 집합

2) E(G) : 정점간을 잇는 edge의 집합

  • 그래프의 특징

1) 자기간선(self-edge)를 가질 수 없다.

다시말해 (v,v) or <v,v>를 가질 수 없다.

2) 두 정점 사이에 같은 에지를 여러개 가질 수 없다.

이런 특징이 있는 그래프를 다중그래프라고 한다.

2. 그래프의 종류

1) 무방향 그래프

1

2) 방향 그래프

2

3. adjacent와 incident

3

4. 경로와 길이

4

5. cycle과 connected

5

6. degree(차수) : 정점 v에 부속된 에지의 수

아래의 경우 차수는?

6

7. in-degree와 out-degree

7

8. subgraph(부분그래프)와 spanning subgraph (신장부분그래프)

8

임의의 그래프 G에 대한 부분그래프중에서 모든 정점과 연결된 부분 그래프를 말한다.

9. 그래프 표현

9

10. Graph traversal(그래프 순회)

그래프 G에서 한 정점 v가 주어졌을때 모든 정점을 중복되지 않게 방문하는 것

1) 너비우선탐색(BFS, Breadth first search)

  • 트리에서 큐를 이용한 레벨순서순회가 예시다.

  • 정점 v에서 시작하여 인접한 정점을 모두 방문하고 방문한 인접 정점에 대해서 모든 인접한 정점을 방문하는 방식, 큐를 이용함

10

2) 깊이우선탐색(DFS, Depth first search)

  • 트리에서 스택을 이용한 전위,중위, 후위 순회가 예시다.

  • 정점 v에서 시작하여 방문하지 않은 정점 중 한 방향으로 쭉 따라간 후 다시 돌아와 방문하지 않은 다른 정점을 따라 다시 쭉 따라간다. 처음 시작한 정점에 돌아오고 더 이상 방문할 정점이 남아있지 않다면 종료

11

step 1) 3번이 스타트 위치라고 치자, 현재 위치에서 3 스텍 프레임이 쌓인다. top이 3을 가리킴. top이 현재 가리키는 것이 현재 위치라고 할 수 있다.

step 2) 그리고 0번으로 이동한다고 하면 0 스텍프레임이 3스텍 프레임 위로 쌓인다. top이 0을 가리킴

step 3) 그 다음에 1로 이동한다고 하면 1번이 가장 위의 스텍프레임으로 쌓이게 된다. top이 1을 가리킴.

  • 파이썬 코드로 구현실습

graph representation을 이용하여 구현할 수 있어야 한다.

from queue1 import Queue
from stack1 import Stack

class GNode:
    def __init__(self, vertex=None):
        self.vertex=vertex
        self.link=None

class Graph:
    def __init__(self):
        #인접 리스트로 구현
        self.adjacency_list=[]
        #방문 여부 체크
        self.visited=[]

    def add_vertex(self, vnum=1):
        self.adjacency_list.extend([None for _ in range(vnum)])
        self.visited.extend([False for _ in range(vnum)])
    
    def __add_node(self, vertex, node):
        cur=self.adjacency_list[vertex]
        #아직 에지가 하나도 없다면
        if not cur:
            self.adjacency_list[vertex]=node
        else:
            while cur.link:
                cur=cur.link
            cur.link=node

    def insert_edge(self, u, v):
        unode=GNode(u)
        vnode=GNode(v)

        self.__add_node(u, vnode)
        self.__add_node(v, unode)

    def init_visited(self):
        for i in range(len(self.visited)):
            self.visited[i]=False

    def bfs(self, v):
        
        ## step1) 큐를 하나만든다.
        ## step2) 큐에다가 첫번째 인덱스(시작 정점)를 삽입한다.
        ## step3) visited라는 array에 전부 false로 채워져 있는데
        ##        visited의 v에 True를 할당한다.
        ## step4) 큐가 비어있을때까지 while문이 돌것이다.
        ## step5) 큐에서 v를 하나 받아오는데 이 방식을 큐에서 dequeue하는 방식
        ## step6) v를 방문했다고 체크 (print문)
        ## step7) adjacency_list를 u로 할당하여 얻어온다.
        ## step8) u가 방문하지 않았으면 visited를 True로 바꿔준다.
        ## step9) u를 u.link로 만들어준다.
        
        q=Queue()
        #방문 체크 리스트를 초기화한다
        #O(v)
        self.init_visited()

        #첫번째 정점을 큐에 넣고
        #방문 체크
        q.enqueue(v)
        self.visited[v]=True
        
        # 방문
        print(v, end="  ")

        while not q.empty():
            v=q.dequeue()
            #인접 리스트를 얻어온다
            u=self.adjacency_list[v]
            while u:
                #아직 방문하지 않은 노드라면
                #큐에 넣고 방문 체크!
                if not self.visited[u.vertex]:
                    q.enqueue(u.vertex)
                    self.visited[u.vertex]=True
                u=u.link

    def __dfs_recursion(self, v):
        #방문
        print(v, end="  ")
        #방문 체크
        self.visited[v]=True

        u=self.adjacency_list[v]
        while u:
            if not self.visited[u.vertex]:
                self.__dfs_recursion(u.vertex)
            u=u.link

    def dfs(self, v):
        self.init_visited()
        self.__dfs_recursion(v)

    def iter_dfs(self, v):
        ## iter : 결국에는 while문이나for문 쓰겠다.
        ## v는 스타팅 포인트
        
        """
        시작 정점으로 돌아가 
        더 이상 방문할 정점이 없어야 종료
        """
        
        s=Stack() 
        # 스텍을 이용할 것이기 때문에 스텍을 하나 만듬
        
        self.init_visited() 
        ## 내부적으로 visited라는 함수를 이용해서 visit 배열을 false로 초기화

        s.push(v)
        ## 방문 체크 및 방문
        self.visited[v]=True
        print(v, end="  ")

        ## 스텍이 비어있지 않는 이상 계속 반복됨
        while not s.empty():
            
            is_moved = False
            
            v = s.peek()
            ## pop을 한다는 것은 스텍을 날려서 전단계로 돌아간다는 
            ## 의미니까 아직은 그럴 단계는 아니고 그냥 peek로 현재 위치를 받아옴
            
            ## v와 연결된 모든 인접 리스트를 받아온다.
            u = self.adjacency_list[v]
            ## u로 받아온것은 adjacency의 single_linked_list로 되어 있다.
            
            while u:
                
                if not self.visited[u.vertex]:
                    ## u가 아직 방문하지 않았다면
                    
                    s.push(u.vertex)
                    #방문 체크 및 방문
                    
                    self.visited[u.vertex]=True
                    ## 다음 노드로 이동했다는 것을 의미함
                    
                    print(u.vertex, end="  ")
                    #아직 방문하지 않은 정점을 방문했으므로
                    
                    is_moved=True
                    ## 방문했다는 의미는 이동을 했다는 의미이므로 is_moved를
                    ## True로 만든다.
                    
                    break
                    
                u = u.link
            
            if not is_moved: 
                ## while을 빠져나왔음에도 불구하고 is_move가 false라는 의미는
                ## 이동할 곳이 없다는 의미이므로 pop해서 back해주면 된다.
                s.pop()
                

## < visited >
## 배열이고 노드 6개가 [F,F,F,F,F,F]로 되어 있으며 인덱스가 vertex다
## 방문했으면 False가 True로 바뀐다

## < is_moved >
## 노드가 갈 수 있는 곳을 다 갔는가를 체크하는 용도

g=Graph()
g.add_vertex(6)
g.insert_edge(1, 0)
g.insert_edge(0, 3)
g.insert_edge(3, 4)
g.insert_edge(4, 2)
g.insert_edge(2, 5)
  • BFS
#예상 출력 결과 : 3  0  4  1  2  5
g.bfs(3)
3  0  4  1  2  5  
  • DFS
#예상 출력 결과 : 3  0  1  4  2  5
g.dfs(3)
3  0  1  4  2  5  
  • iter_DFS
g.iter_dfs(3)
3  0  1  4  2  5