그래프를 메모리에 저장하는 방법에는 세 가지가 있습니다.
- 객체로서의 노드와 포인터로서의 모서리
- 번호가 매겨진 노드 x와 노드 y 사이의 모든 간선 가중치를 포함하는 행렬
- 번호가 매겨진 노드 사이의 간선 목록
나는 세 가지를 모두 쓰는 법을 알고 있지만 각각의 장단점을 모두 생각했는지 확신하지 못합니다.
그래프를 메모리에 저장하는 이러한 각 방법의 장점과 단점은 무엇입니까?
그래프를 메모리에 저장하는 방법에는 세 가지가 있습니다.
나는 세 가지를 모두 쓰는 법을 알고 있지만 각각의 장단점을 모두 생각했는지 확신하지 못합니다.
그래프를 메모리에 저장하는 이러한 각 방법의 장점과 단점은 무엇입니까?
답변:
이를 분석하는 한 가지 방법은 메모리 및 시간 복잡도 (그래프에 액세스하려는 방법에 따라 다름) 측면에서입니다.
노드를 서로에 대한 포인터가있는 객체로 저장
간선 가중치 행렬 저장
그래프에서 실행하는 알고리즘과 노드 수에 따라 적절한 표현을 선택해야합니다.
고려해야 할 몇 가지 사항이 더 있습니다.
행렬 모델은 행렬에 가중치를 저장하여 가중치가 적용된 간선이있는 그래프에 더 쉽게 적합합니다. 객체 / 포인터 모델은 포인터 배열과의 동기화가 필요한 병렬 배열에 가장자리 가중치를 저장해야합니다.
개체 / 포인터 모델은 포인터가 쌍으로 유지되어야하므로 비 동기화 될 수 있으므로 방향이없는 그래프보다 방향성 그래프에서 더 잘 작동합니다.
객체 및 포인터 방법은 일부 사람들이 지적했듯이 검색이 어렵지만 추가 구조가 많은 이진 검색 트리 구축과 같은 작업을 수행하는 데는 매우 자연 스럽습니다.
저는 개인적으로 인접 행렬을 좋아합니다. 대수 그래프 이론의 도구를 사용하여 모든 종류의 문제를 훨씬 쉽게 만들어주기 때문입니다. (인접 행렬의 k 번째 거듭 제곱은 정점 i에서 정점 j까지 길이 k의 경로 수를 제공합니다. 길이 <= k 인 경로 수를 얻으려면 k 제곱을 취하기 전에 단위 행렬을 추가하십시오. 라플라시안의 n-1 마이너로 스패닝 트리의 수를 구합니다 ... 등등.)
그러나 모든 사람들은 인접 행렬이 메모리 비싸다고 말합니다! 절반 정도만 오른쪽입니다. 그래프에 간선이 거의없는 경우 희소 행렬을 사용하여이 문제를 해결할 수 있습니다. 희소 행렬 데이터 구조는 인접 목록을 유지하는 작업을 정확히 수행하지만 여전히 표준 행렬 작업의 전체 영역을 사용할 수 있으므로 두 가지 장점을 모두 제공합니다.
첫 번째 예제는 객체로서의 노드와 포인터로서의 에지라는 약간 모호하다고 생각합니다. 일부 루트 노드에 대한 포인터 만 저장하여이를 추적 할 수 있습니다.이 경우 주어진 노드에 액세스하는 것은 비효율적 일 수 있습니다 (예를 들어 노드 4를 원합니다. 노드 객체가 제공되지 않으면 검색해야 할 수 있습니다) . 이 경우 루트 노드에서 도달 할 수없는 그래프 부분도 손실됩니다. 나는 이것이 f64 rainbow가 주어진 노드에 액세스하기위한 시간 복잡도가 O (n)라고 말할 때 가정하는 경우라고 생각합니다.
그렇지 않으면 각 노드에 대한 포인터로 가득 찬 배열 (또는 해시 맵)을 유지할 수도 있습니다. 이것은 주어진 노드에 대한 O (1) 액세스를 허용하지만 메모리 사용량을 약간 증가시킵니다. n이 노드의 수이고 e가 엣지의 수이면이 접근 방식의 공간 복잡도는 O (n + e)가됩니다.
행렬 접근법의 공간 복잡도는 O (n ^ 2) 선을 따릅니다 (가장자리가 단방향이라고 가정). 그래프가 희소하면 행렬에 빈 셀이 많이 있습니다. 그러나 그래프가 완전히 연결되어 있다면 (e = n ^ 2), 이것은 첫 번째 접근 방식과 유리하게 비교됩니다. RG가 말했듯이 매트릭스를 하나의 메모리 청크로 할당하면이 접근 방식을 사용하면 캐시 미스가 줄어들 수 있으므로 그래프 주변의 많은 에지를 더 빠르게 따라갈 수 있습니다.
세 번째 접근 방식은 대부분의 경우 O (e)에서 가장 공간 효율적이지만 주어진 노드의 모든 가장자리를 O (e) 번거롭게 찾을 수 있습니다. 이것이 매우 유용한 경우는 생각할 수 없습니다.
또 다른 옵션이 있습니다 : 노드는 객체로, 모서리는 객체로도, 각 모서리는 두 개의 이중 연결 목록에 동시에 있습니다. 동일한 노드에서 나오는 모든 모서리의 목록과 같은 노드로가는 모든 모서리의 목록 .
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 개)
구조는 또한 다소 일반적인 그래프를 나타낼 수 있습니다. 루프가있는 지향 멀티 그래프 (즉, 여러 개의 개별 루프를 포함하는 동일한 두 노드 사이에 여러 개의 개별 에지 (x에서 x로가는 에지)를 가질 수 있음).
좋습니다. 모서리에 가중치가 없으면 행렬은 이진 배열이 될 수 있으며 이항 연산자를 사용하면이 경우 작업을 정말, 정말 빠르게 진행할 수 있습니다.
그래프가 희소하면 객체 / 포인터 방법이 훨씬 더 효율적으로 보입니다. 객체 / 포인터를 데이터 구조에 포함하여 특별히 단일 메모리 청크에 넣는 것도 좋은 계획이거나 함께 유지하는 다른 방법 일 수 있습니다.
인접 노드 목록 (단순히 연결된 노드 목록)은 가장 메모리 효율적이지만 가장 느릴 수도 있습니다.
유향 그래프 돌리는 것은 쉬운 매트릭스로 표현하고, 쉽게 인접리스트와 함께, 그러나 개체 / 포인터 표현 너무 크지 않다.