그래프를 메모리에 저장하는 세 가지 방법, 장단점


90

그래프를 메모리에 저장하는 방법에는 세 가지가 있습니다.

  1. 객체로서의 노드와 포인터로서의 모서리
  2. 번호가 매겨진 노드 x와 노드 y 사이의 모든 간선 가중치를 포함하는 행렬
  3. 번호가 매겨진 노드 사이의 간선 목록

나는 세 가지를 모두 쓰는 법을 알고 있지만 각각의 장단점을 모두 생각했는지 확신하지 못합니다.

그래프를 메모리에 저장하는 이러한 각 방법의 장점과 단점은 무엇입니까?


3
그래프가 매우 연결되어 있거나 매우 작은 경우에만 행렬을 고려합니다. 드물게 연결된 그래프의 경우 객체 / 포인터 또는 간선 목록 접근 방식 모두 훨씬 더 나은 메모리 사용을 제공합니다. 내가 간과했던 스토리지 외에 무엇이 궁금합니다. ;)
sarnold 2010

2
시간 복잡성도 다르며 행렬은 O (1)이며 다른 표현은 찾고있는 항목에 따라 크게 다를 수 있습니다.
msw 2010-07-20

1
포인터 목록에 대한 매트릭스로 그래프를 구현할 때의 하드웨어 이점을 설명하는 기사를 읽은 적이 있습니다. 연속적인 메모리 블록을 처리 할 때 작업 세트의 상당 부분이 L2 캐시에있을 수 있다는 점을 제외하고는 그에 대해 많이 기억할 수 없습니다. 반면에 노드 / 포인터 목록은 메모리를 통해 샷건 될 수 있으며 캐시에 도달하지 않는 가져 오기가 필요할 수 있습니다. 동의하는지 잘 모르겠지만 흥미로운 생각입니다.
nerraga 2010

1
@Dean J : "노드를 객체로, 모서리를 포인터로 표현"에 대한 질문입니다. 객체에 포인터를 저장하는 데 어떤 데이터 구조를 사용합니까? 목록입니까?
Timofey

4
일반적인 이름은 다음과 같습니다 : (1) adjacency list , (2) adjacency matrix , (3) edge list .
Evgeni Sergeev

답변:


51

이를 분석하는 한 가지 방법은 메모리 및 시간 복잡도 (그래프에 액세스하려는 방법에 따라 다름) 측면에서입니다.

노드를 서로에 대한 포인터가있는 객체로 저장

  • 이 접근 방식의 메모리 복잡성은 노드가있는만큼 많은 객체를 가지고 있기 때문에 O (n)입니다. 각 노드 객체는 최대 n 개의 노드에 대한 포인터를 포함 할 수 있으므로 필요한 포인터 (노드)의 수는 최대 O (n ^ 2)입니다.
  • 이 데이터 구조의 시간 복잡도는 주어진 노드에 액세스하기위한 O (n)입니다.

간선 가중치 행렬 저장

  • 이것은 행렬에 대한 O (n ^ 2)의 메모리 복잡도입니다.
  • 이 데이터 구조의 장점은 주어진 노드에 액세스하는 시간 복잡도가 O (1)이라는 것입니다.

그래프에서 실행하는 알고리즘과 노드 수에 따라 적절한 표현을 선택해야합니다.


3
객체 / 포인터 모델에서 검색에 대한 시간 복잡도는 노드를 별도의 배열에 저장하는 경우에만 O (n)이라고 생각합니다. 그렇지 않으면 원하는 노드를 검색하는 그래프를 탐색해야합니다. 임의의 그래프에서 모든 노드 (모든 에지는 아님)를 순회하는 것은 O (n)에서 수행 할 수 없습니다.
Barry Fruitman 2013

@BarryFruitman 나는 당신이 정확하다고 확신합니다. BFS는 O (V + E)입니다. 또한 다른 노드에 연결되지 않은 노드를 검색하는 경우에는 찾을 수 없습니다.
WilderField

10

고려해야 할 몇 가지 사항이 더 있습니다.

  1. 행렬 모델은 행렬에 가중치를 저장하여 가중치가 적용된 간선이있는 그래프에 더 쉽게 적합합니다. 객체 / 포인터 모델은 포인터 배열과의 동기화가 필요한 병렬 배열에 가장자리 가중치를 저장해야합니다.

  2. 개체 / 포인터 모델은 포인터가 쌍으로 유지되어야하므로 비 동기화 될 수 있으므로 방향이없는 그래프보다 방향성 그래프에서 더 잘 작동합니다.


1
포인터가 방향이없는 그래프와 쌍으로 유지되어야한다는 뜻입니다. 맞습니까? 지시 된 경우 특정 정점의 인접 목록에 정점을 추가하기 만하면되지만 방향이 지정되지 않은 경우 두 정점의 인접 목록에 하나를 추가해야합니까?
FrostyStraw 2017

@FrostyStraw 네, 정확합니다.
Barry Fruitman

8

객체 및 포인터 방법은 일부 사람들이 지적했듯이 검색이 어렵지만 추가 구조가 많은 이진 검색 트리 구축과 같은 작업을 수행하는 데는 매우 자연 스럽습니다.

저는 개인적으로 인접 행렬을 좋아합니다. 대수 그래프 이론의 도구를 사용하여 모든 종류의 문제를 훨씬 쉽게 만들어주기 때문입니다. (인접 행렬의 k 번째 거듭 제곱은 정점 i에서 정점 j까지 길이 k의 경로 수를 제공합니다. 길이 <= k 인 경로 수를 얻으려면 k 제곱을 취하기 전에 단위 행렬을 추가하십시오. 라플라시안의 n-1 마이너로 스패닝 트리의 수를 구합니다 ... 등등.)

그러나 모든 사람들은 인접 행렬이 메모리 비싸다고 말합니다! 절반 정도만 오른쪽입니다. 그래프에 간선이 거의없는 경우 희소 행렬을 사용하여이 문제를 해결할 수 있습니다. 희소 행렬 데이터 구조는 인접 목록을 유지하는 작업을 정확히 수행하지만 여전히 표준 행렬 작업의 전체 영역을 사용할 수 있으므로 두 가지 장점을 모두 제공합니다.


7

첫 번째 예제는 객체로서의 노드와 포인터로서의 에지라는 약간 모호하다고 생각합니다. 일부 루트 노드에 대한 포인터 만 저장하여이를 추적 할 수 있습니다.이 경우 주어진 노드에 액세스하는 것은 비효율적 일 수 있습니다 (예를 들어 노드 4를 원합니다. 노드 객체가 제공되지 않으면 검색해야 할 수 있습니다) . 이 경우 루트 노드에서 도달 할 수없는 그래프 부분도 손실됩니다. 나는 이것이 f64 rainbow가 주어진 노드에 액세스하기위한 시간 복잡도가 O (n)라고 말할 때 가정하는 경우라고 생각합니다.

그렇지 않으면 각 노드에 대한 포인터로 가득 찬 배열 (또는 해시 맵)을 유지할 수도 있습니다. 이것은 주어진 노드에 대한 O (1) 액세스를 허용하지만 메모리 사용량을 약간 증가시킵니다. n이 노드의 수이고 e가 엣지의 수이면이 접근 방식의 공간 복잡도는 O (n + e)가됩니다.

행렬 접근법의 공간 복잡도는 O (n ^ 2) 선을 따릅니다 (가장자리가 단방향이라고 가정). 그래프가 희소하면 행렬에 빈 셀이 많이 있습니다. 그러나 그래프가 완전히 연결되어 있다면 (e = n ^ 2), 이것은 첫 번째 접근 방식과 유리하게 비교됩니다. RG가 말했듯이 매트릭스를 하나의 메모리 청크로 할당하면이 접근 방식을 사용하면 캐시 미스가 줄어들 수 있으므로 그래프 주변의 많은 에지를 더 빠르게 따라갈 수 있습니다.

세 번째 접근 방식은 대부분의 경우 O (e)에서 가장 공간 효율적이지만 주어진 노드의 모든 가장자리를 O (e) 번거롭게 찾을 수 있습니다. 이것이 매우 유용한 경우는 생각할 수 없습니다.


에지 목록은 Kruskal의 알고리즘에 자연 스럽습니다 ( "각 에지에 대해 union-find에서 조회"). 또한 Skiena (2nd ed., page 157)는 그의 라이브러리 Combinatorica ( 많은 알고리즘의 범용 라이브러리) 에서 그래프의 기본 데이터 구조로 에지 목록에 대해 이야기 합니다. 그는 그 이유 중 하나가 Combinatorica가 살고있는 환경 인 Mathematica의 계산 모델에 의해 부과 된 제약 때문이라고 언급합니다.
Evgeni Sergeev 2016

5

위키피디아의 비교표를 살펴보세요 . 그래프의 각 표현을 언제 사용할지 꽤 잘 이해할 수 있습니다.


4

또 다른 옵션이 있습니다 : 노드는 객체로, 모서리는 객체로도, 각 모서리는 두 개의 이중 연결 목록에 동시에 있습니다. 동일한 노드에서 나오는 모든 모서리의 목록과 같은 노드로가는 모든 모서리의 목록 .

struct Node {
    ... node payload ...
    Edge *first_in;    // All incoming edges
    Edge *first_out;   // All outgoing edges
};

struct Edge {
    ... edge payload ...
    Node *from, *to;
    Edge *prev_in_from, *next_in_from; // dlist of same "from"
    Edge *prev_in_to, *next_in_to;     // dlist of same "to"
};

메모리 오버 헤드가 크지 만 (노드 당 포인터 2 개, 에지 당 포인터 6 개)

  • O (1) 노드 삽입
  • O (1) 가장자리 삽입 ( "from"및 "to"노드에 대한 포인터 제공)
  • O (1) 가장자리 삭제 (포인터 제공)
  • O (deg (n)) 노드 삭제 (포인터 제공)
  • O (deg (n)) 노드의 이웃 찾기

구조는 또한 다소 일반적인 그래프를 나타낼 수 있습니다. 루프가있는 지향 멀티 그래프 (즉, 여러 개의 개별 루프를 포함하는 동일한 두 노드 사이에 여러 개의 개별 에지 (x에서 x로가는 에지)를 가질 수 있음).

이 접근 방식에 대한 자세한 설명은 여기에서 확인할 수 있습니다 .


3

좋습니다. 모서리에 가중치가 없으면 행렬은 이진 배열이 될 수 있으며 이항 연산자를 사용하면이 경우 작업을 정말, 정말 빠르게 진행할 수 있습니다.

그래프가 희소하면 객체 / 포인터 방법이 훨씬 더 효율적으로 보입니다. 객체 / 포인터를 데이터 구조에 포함하여 특별히 단일 메모리 청크에 넣는 것도 좋은 계획이거나 함께 유지하는 다른 방법 일 수 있습니다.

인접 노드 목록 (단순히 연결된 노드 목록)은 가장 메모리 효율적이지만 가장 느릴 수도 있습니다.

유향 그래프 돌리는 것은 쉬운 매트릭스로 표현하고, 쉽게 인접리스트와 함께, 그러나 개체 / 포인터 표현 너무 크지 않다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.