본문 바로가기
CS/Datastructure & Algorithm

그래프, 인접행렬과 인접리스트

by Jman 2022. 4. 13.

그래프 (Graph)

수학적 정의로 그래프는 객체들의 쌍들이 서로 연관되어 객체의 집합을 이루는 구조를 의미합니다.

쉽게 설명하면?

사물이나 추상적인 개념 간의 연결 관계를 표현한 것이라고 할 수 있습니다.

도시를 연결하는 도로망이나 사람들 간의 관계, 웹 사이트 링크 관계 등이 이에 해당하는 예시입니다.

 

 

그래프의 구성 요소

  • 정점 (Vertex / Node) : 그래프에서 위치를 나타냅니다.
  • 간선 (Edge / Link / Branch) : 그래프에서 위치 간의 관계를 나타냅니다. 즉, 각 정점(노드)를 연결하는 선(line)을 의미합니다.
  • 인접 정점 (Adjacent Vertex) : 간선(Edge)에 의해 직접 연결된 정점을 의미합니다.
  • G(V, E) : 그래프는 정점과 간선의 집합이므로 이건, 그래프 자체를 의미합니다.

 

그래프의 종류

  • 무방향 그래프 : 간선을 통해 양방향으로 이동할 수 있는 그래프 => G(A, B) == G(B, A) 동일
  • 방향 그래프 : 간선에 방향이 존재하는 그래프 => G(A, B) != G(B,A) 다름
  • 가중치 그래프 : 간선에 비용 또는 가중치가 할당된 그래프 => '네트워크' 라고도 함
  • 연결 그래프 : 무방향 그래프에서 모든 정점쌍들에 대해 항상 경로가 존재하는 그래프
  • 비연결 그래프 : 무방향 그래프에서 특정 정점쌍들에 경로가 존재하지 않는 그래프
  • 순환 그래프 : 단순 경로의 시작 정점과 종료 정점이 동일한 경우   * 단순경로란, 반복되는 정점이 없는 경로
  • 비순환 그래프 : 순환이 없는 그래프

 

인접 행렬과 인접 리스트

그래프를 구현하여 표현하는 방법에는 여러가지가 있습니다.

일반적으로 인접행렬과 인접리스트 방식이 있습니다.

이 두 가지 방식은 서로 정반대의 특징을 갖기 때문에, 한 방식의 장점이 다른 방식의 단점이 되는 경우가 나타납니다.

따라서,

구현하려는 알고리즘, 그래프의 종류에 따라 적절하게 사용하여야 합니다.

 

 

1. 인접 행렬 그래프 - "모든 정보를 저장"

  • 직관적이며 쉽게 구현이 가능합니다.
  • 하지만, 불 필요한 정보의 저장이 많으며, 그래프의 크기가 커지면서 메모리 초과가 발생할 수도 있습니다.

구현방법은 int 형 2차원 배열을 주로 이용하며, 이동할 수 있다면 1, 이동할 수 없다면 0으로 표기합니다.

 

ㅓㅏㅣㅓㅏㅣㅓㅏㅣㅓㅏㅣㅓ

 

인덱스를 가지고 그래프 예시를 들자면, 아래와 같이 생각하면 됩니다.

https://freestrokes.tistory.com/87

정점 인접 정점
1 2
1 3
2 3
3 4
2 4
4 5
3 5
4 6

 

위에서 설명한 인접 행렬을 코드로 구현해보겠습니다.

class ArrGraph {

    private int[][] arrGraph;

    public ArrGraph(int initSize) {

        /*
            배열의 idx 가 0부터 시작함으로, ArrayIndexOutOfBoundException 방지를 위해
            정점을 담는 인접행렬의 행과 열 size 는 1을 더하여 초기화 해줌.
         */

        this.arrGraph = new int[initSize+1][initSize+1];
    }

    public int[][] getArrGraph() {
        return this.arrGraph;
    }

    public void putTwoWay(int x, int y) {
        arrGraph[x][y] = arrGraph[y][x] = 1;
    }

    public void putOneWay(int x, int y) {
        arrGraph[x][y] = 1;
    }

    public void printGraphToAdjArr() {
        for (int i = 0; i < arrGraph.length; i++) {
            for (int j = 0; j < arrGraph[i].length; j++) {
                System.out.print(" " + arrGraph[i][j]);
            }
            System.out.println();
        }
    }
}

public class AdjArray {
    public static void main(String[] args) {
        int initSize = 6;
        ArrGraph adjArr = new ArrGraph(initSize);

        adjArr.putTwoWay(1, 2);
        adjArr.putTwoWay(1, 3);
        adjArr.putTwoWay(2, 3);
        adjArr.putTwoWay(2, 4);
        adjArr.putTwoWay(3, 4);
        adjArr.putTwoWay(3, 5);
        adjArr.putTwoWay(4, 5);
        adjArr.putTwoWay(4, 6);
        
        adjArr.printGraphToAdjArr();
    }
}

2. 인접 리스트 그래프

  • 필요한 정보만 저장하여, 메모리 절약이 가능합니다.
  • 하지만, 인접행렬에 비해 다소 어려움이 있습니다.

인접리스트는 배열(ArrayList)이나, 연결리스트(LinkedList) 등의 자료구조를 이용하여, 각 정점에서 이동가능한 정점들을 저장합니다.

대표적으로 두 자료구조를 이용한 2차원 배열이라고 생각할 수 있습니다.

아래도 위와 같이 인덱스를 가지고 예시를 들어보자면,

 

2 ->    
2 1 -> 3 ->  
3 1 -> 2 -> 4 -> 5
4 2 -> 3 -> 5 -> 6
5 3 -> 4 ->    
6 4 ->      

 

위 상황을 코드로 구현로 나타내보겠습니다.

 

import com.sun.scenario.effect.impl.sw.java.JSWBlend_SRC_OUTPeer;

import java.util.ArrayList;

class ListGraph {
    private ArrayList<ArrayList<Integer>> listGraph;

    public ListGraph(int initSize) {
        this.listGraph = new ArrayList<ArrayList<Integer>>();

        // 안에있는 값들을 초기화를 해줍니다. 그렇지만 +1 필수!
        for (int i = 0; i < initSize + 1; i++) {
            listGraph.add(new ArrayList<>());
        }
    }

    public ArrayList<ArrayList<Integer>> getGraph() {
        return this.listGraph;
    }
    public ArrayList<Integer> getNode(int i ) {
        return this.listGraph.get(i);
    }

    // 그래프 추가 (양방향)
    public void putTwoWay(int x, int y) {
        listGraph.get(x).add(y);
        listGraph.get(y).add(x);
    }

    // 그래프 추가 (단방향)
    public void putOneWay(int x, int y) {
        listGraph.get(x).add(y);
    }

    public void printGraphToAdjList() {
        for (int i = 1; i < listGraph.size(); i++) {
            System.out.print("정점 " + i + ": ");
            for (int j = 0; j < listGraph.get(i).size(); j++) {
                System.out.print(" --> " + listGraph.get(i).get(j));
            }
            System.out.println();
        }
    }
}


public class AdjList {
    public static void main(String[] args) {
        int initSize = 6;
        ListGraph adjList = new ListGraph(initSize);

        // 단방향
        // 따로..
        
        
        // 양방향
        adjList.putTwoWay(1, 2);
        adjList.putTwoWay(1, 3);
        adjList.putTwoWay(2, 3);
        adjList.putTwoWay(2, 4);
        adjList.putTwoWay(3, 4);
        adjList.putTwoWay(3, 5);
        adjList.putTwoWay(4, 5);
        adjList.putTwoWay(4, 6);
        
        //System.out.println(adjList.getGraph());
        adjList.printGraphToAdjList();
    }
}

 

인접 행렬과 인접리스트의 차이

인접행렬

  • 정점 v1, v2에 대해 한 번의 접근으로 확인 가능합니다
  • V개의 노드 표현을 위해 V 제곱 만큼의 공간이 필요합니다.
  • 인접 노드를 찾기 위해선, 모든 노드를 순회해야 합니다.
  • 공간 복잡도는 O(V제곱) 입니다.

인접리스트

  • 리스트의 처음부터 하나 씩 확인해야 합니다.
  • V개의 리스트에 간선(E) 만큼 원소가 들어 있습니다.
  • 인접 노드를 쉽게 찾을 수 있습니다.
  • 공간 복잡도는 O(V + E) 입니다.

'CS > Datastructure & Algorithm' 카테고리의 다른 글

HashMap & HashTable  (0) 2022.04.18
이분 그래프(Bipartite Graph)란?  (0) 2022.04.15
그래프와 트리의 차이는?  (0) 2022.04.04
백트래킹(Backtracking) 이란?  (0) 2022.04.01
점화식 이란?  (0) 2022.03.16