노드(N, node)와 그 노드를 연결하는 간선(E, edge)를 하나로 모아 놓은 자료구조.
➜ 즉, 연결되어 있는 객체 간의 관계를 표현할 수 있는 자료 구조.
그래프를 이야기하면 트리와의 차이를 보아야 한다.
<그래프와 트리의 차이>

트리와, 그래프의 차이 중 가장 큰 것은 순환의 여부를 파악하면 된다.
순환이 가능하면 그래프, 순환이 불가능하면 트리
물론, 그래프도 비순환 그래프도 존재한다. 즉, 트리도 그래프의 한 부분이라고 볼 수 있다.

Vertex A 와 Vertex B 를 연결하는 간선은 (A,B)로 표기A → B로 갈 수 있는 간선은 <A, B>로 표기정점 수 : n이면, 간선의 수 : n * (n-1) / 2
adj[i][j]: 노드 i에서 j로 가는 간선이 존재할 경우1, 아니면0
만약 가중치 그래프라면,1대신 가중치를 넣으면 된다
무방향 그래프 라면, i → j / j → i 둘 다 가능하므로, 행렬이 대칭을 이룰 것이다.

노드의 개수, 간선의 개수, 연결 정보가 주어진다면
4 5 # 노드의 개수, 간선의 개수 1 2 # 연결 정보 2 3 1 4 1 3
from sys import stdin
node, edge = map(int, stdin.readline().split())
adj = [[0 for _ in range(node+1) for _ in range(node+1)]
for _ in range(edge):
src, dest = map(int, stdin.readline().split())
adj[src][dest] = 1
adj[dest][src] = 1
파이썬의 리스트에서 append을 통해서 구현이 가능하다.

이중 리스트에 연결되어 있는 노드를 기록하는 방식이다.
노드의 개수, 간선의 개수, 연결 정보가 주어진다면
4 5 # 노드의 개수, 간선의 개수 1 2 # 연결 정보 2 3 1 4 1 3
from sys import stdin
node, edge = map(int, stdin.readline().split())
adj = [ [] for _ in range(node+1)]
for _ in range(edge):
src, dest = map(int, stdin.readline().split())
adj[src].append(dest)
adj[dest].append(src)
adj = [["B", "C"], ["A", "C", "D"], ["A", "B"], ["B"]]
이제 두 코드의 시간 복잡도를 비교해보겠습니다:
O(node^2 + edge)O(node + edge)일반적으로, 노드 수 node가 많고 간선 수 edge가 적을 경우, 인접 리스트를 사용하는 첫 번째 코드가 더 효율적입니다. 반면에, 노드 수 node가 적고 간선 수 edge가 많은 경우, 인접 행렬을 사용하는 두 번째 코드가 더 효율적일 수 있습니다.