읽기 전에 알아두면 좋아요
아래 개념이 익숙하면 본문을 바로 읽으세요. 낯선 개념만 링크에서 복습하면 됩니다.
- JavaScript 그래프 구현: 인접 리스트·인접 행렬 비교와 BFS그래프는 점인 정점과 점 사이의 연결인 간선으로 관계를 나타냅니다. 전체 정점에서 방문을 시작해 고립 정점까지 연결 성분을 셉니다.
- 큐와 FIFO: head 인덱스로 JavaScript 대기열 만들기큐는 먼저 넣은 값을 먼저 꺼냅니다. 대기열에 넣고 꺼낼 때의 순서를 확인합니다. 먼저 들어온 값을 먼저 꺼내는 큐의 동작을 확인합니다.
개념과 예제는 실행하지 않아도 읽을 수 있습니다. 내 컴퓨터·터미널에서 직접 실행하는 과정은 선택 실습입니다.
과정 위치와 선택 복습 안내
과정 위치: 트리·그래프와 최단거리 · 문제
과정 위치는 이 글이 속한 묶음입니다. 이 단계의 모든 개념을 먼저 읽을 필요는 없습니다. 위에 연결한 개념 중 낯선 내용만 복습하세요. 문제의 조건·상태·갱신 순서는 익숙한 언어로 설명하면 됩니다.
JavaScript로 문법·실행 복습하기 (선택)
개념 글의 코드는 JavaScript 예시입니다. 문제 화면에서는 JavaScript·Python·Java·C++·C# 중 익숙한 언어의 함수를 작성하세요. 본문의 Node.js 입출력 예시는 로컬 실행을 위한 별도 안내입니다.
연결 구역 크기 문제에서는 그래프의 어떤 값을 구해 어떤 순서로 반환하나요?
그래프에서 서로 연결된 구역의 크기를 구해 작은 순서로 반환하는 문제입니다. 그래프·방문 상태·큐의 기본을 준비하고, 아래 입력·반환 계약을 확인해 직접 풀어보세요.
적용 조건·범위: 정답은 정점 중복과 목록에 없는 끝점을 예외로 거부합니다. 그 밖의 입력 형식과 크기는 제약을 만족한다고 가정하며 일반 인터넷 요청용 전체 스키마 검증기는 아닙니다. V=0이면 edges도 빈 배열이고 출력은 []입니다.
독립 창작 문제
이 문제의 상황·입출력 계약·예시는 이 글을 위해 직접 구성했습니다. 공식 문제를 번역하거나 복제한 지문이 아닙니다. 구현 개념은 JavaScript 그래프 구현: 인접 리스트·인접 행렬 비교와 BFS에서 먼저 확인할 수 있습니다.
정점 목록 vertices와 무방향 간선 목록 edges가 주어집니다. 간선을 따라 서로 도달할 수 있는 정점들을 하나의 연결 구역으로 묶고, 각 구역의 정점 수를 작은 것부터 출력하세요. 어떤 간선에도 나오지 않는 정점도 크기 1인 구역입니다.
같은 간선이 여러 번 등장하거나 정점 자신으로 돌아오는 간선이 있어도 구역에 속한 정점을 중복해서 세지 않습니다. 구역의 이름이나 내부 정점 순서는 출력하지 않으며, 크기가 같은 구역이 여러 개면 그 크기를 여러 번 출력합니다.
입력·출력과 제약
표준 입력은 {vertices,edges} JSON 객체입니다. vertices는 중복 없는 문자열 배열이며 V는 0~10000, 각 식별자 길이는 1~50입니다. edges는 [from,to] 쌍의 배열이며 E는 0~50000입니다. 모든 끝점은 vertices에 있어야 합니다. 자기 루프와 중복 간선은 허용됩니다. 표준 출력은 구역 크기의 오름차순 JSON 배열입니다.
정답은 정점 중복과 목록에 없는 끝점을 예외로 거부합니다. 그 밖의 입력 형식과 크기는 제약을 만족한다고 가정하며 일반 인터넷 요청용 전체 스키마 검증기는 아닙니다. V=0이면 edges도 빈 배열이고 출력은 []입니다.
입력 예시
{"vertices":["a","b","c","d","e","f"],"edges":[["a","b"],["b","c"],["d","e"],["a","b"],["f","f"]]}
출력 예시
[1,2,3]
실행 방법
Node.js CommonJS 환경에서 정답의 전체 코드를 graph-problem.cjs로 저장합니다. 입력 예시를 UTF-8 input.json 파일로 저장한 뒤 Windows PowerShell에서는 아래 명령을 실행하세요. 내장 node:fs 외에 다른 파일이나 패키지가 필요하지 않습니다.
Get-Content -Raw -Encoding UTF8 ./input.json | node ./graph-problem.cjs
macOS·Linux의 Bash에서는 아래 입력 리다이렉션을 사용할 수 있습니다. 이 < 문법은 PowerShell 명령이 아닙니다.
node ./graph-problem.cjs < input.json
힌트
vertices를 먼저 그래프에 등록한 뒤 간선을 연결하세요. 바깥 반복으로 모든 정점을 보면서 아직 방문하지 않은 정점에서만 BFS를 시작합니다. BFS마다 새 큐를 만들되 visited는 모든 구역에서 공유해야 합니다.
직접 풀어보기
먼저 선택한 언어의 입력·반환 계약에 맞춰 작성해 보세요. 힌트 받기와 정답과 비교하기는 코드를 실행하지 않는 AI 분석입니다. 해답·해설은 아래에서 필요할 때 펼칠 수 있습니다.
AI 힌트
코드를 실행한 채점 결과가 아닌 AI의 코드 분석입니다.
해답·해설 보기
'use strict';
class Graph {
constructor(directed = false) {
this.directed = directed;
this.adj = new Map();
}
addVertex(vertex) {
if (typeof vertex !== 'string') throw new TypeError('vertex must be a string');
if (! this.adj.has(vertex)) this.adj.set(vertex,[]);
return this;
}
addEdge(from, to) {
this.addVertex(from);
this.addVertex(to);
this.adj.get(from).push(to);
if (! this.directed) this.adj.get(to).push(from);
return this;
}
hasEdge(from, to) {
return this.adj.get(from)?.includes(to) ?? false;
}
toMatrix() {
const vertices = [... this.adj.keys()];
const indices = new Map(vertices.map((vertex, index) => [vertex, index]));
const matrix = Array.from( {
length : vertices.length
},
() => new Array(vertices.length).fill(0));
for (const[from, neighbors] of this.adj) {
for (const to of neighbors) matrix[indices.get(from)][indices.get(to)] = 1;
}
return {
vertices, matrix
};
}
bfs(start) {
if (! this.adj.has(start)) return [];
const visited = new Set([start]);
const queue = [start];
let head = 0;
while (head < queue.length) {
const vertex = queue[head];
head += 1;
for (const next of this.adj.get(vertex)) {
if (visited.has(next)) continue;
visited.add(next);
queue.push(next);
}
}
return queue;
}
}
function solve( {
vertices, edges
}) {
const graph = new Graph();
for (const vertex of vertices) graph.addVertex(vertex);
if (graph.adj.size !== vertices.length) throw new TypeError('duplicate vertex');
for (const[from, to] of edges) {
if (! graph.adj.has(from) || ! graph.adj.has(to)) {
throw new TypeError('edge endpoint is not in vertices');
}
graph.addEdge(from, to);
}
const visited = new Set();
const sizes = [];
for (const start of vertices) {
if (visited.has(start)) continue;
const queue = [start];
visited.add(start);
let head = 0;
while (head < queue.length) {
const vertex = queue[head];
head += 1;
for (const next of graph.adj.get(vertex)) {
if (visited.has(next)) continue;
visited.add(next);
queue.push(next);
}
}
sizes.push(queue.length);
}
return sizes.sort((a, b) => a - b);
}
원문 JSON 객체를 파싱한 객체 input을 solve(input)에 전달하고 배열을 반환합니다. 표준 입출력은 포함하지 않습니다.
from collections import deque
def solve(input):
vertices, edges = input['vertices'], input['edges']
adj = {v: [] for v in vertices}
if len(adj) != len(vertices): raise ValueError('duplicate vertex')
for a,b in edges:
if a not in adj or b not in adj: raise ValueError('unknown endpoint')
adj[a].append(b)
adj[b].append(a)
seen, sizes = set(), []
for start in vertices:
if start in seen: continue
seen.add(start)
q, size = deque([start]), 0
while q:
v = q.popleft()
size += 1
for w in adj[v]:
if w not in seen:
seen.add(w)
q.append(w)
sizes.append(size)
return sorted(sizes)
원문 JSON 객체를 파싱한 dict input을 solve(input)에 전달하고 list를 반환합니다. 표준 입출력은 포함하지 않습니다.
import java.util.*;
class Solution {
public int[] solve(String[] vertices, String[][] edges) {
Map < String, List < String >> adj = new HashMap < >();
for (String v : vertices) {
if (adj.put(v, new ArrayList < >()) != null) throw new IllegalArgumentException("duplicate vertex");
}
for (String[] e : edges) {
if (! adj.containsKey(e[0]) || ! adj.containsKey(e[1])) throw new IllegalArgumentException("unknown endpoint");
adj.get(e[0]).add(e[1]);
adj.get(e[1]).add(e[0]);
}
Set < String > seen = new HashSet < >();
List < Integer > out = new ArrayList < >();
for (String s : vertices) {
if (! seen.add(s)) continue;
Queue < String > q = new ArrayDeque < >();
q.add(s);
int count = 0;
while (! q.isEmpty()) {
String v = q.remove();
count++;
for (String w : adj.get(v)) if (seen.add(w)) q.add(w);
}
out.add(count);
}
Collections.sort(out);
return out.stream().mapToInt(Integer::intValue).toArray();
}
}
vertices 문자열 배열과 edges 두 문자열 쌍 배열을 solve에 전달하여 int[]를 반환합니다. JSON 파서는 필요하지 않습니다.
#include <algorithm>
#include <string>
#include <vector>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <stdexcept>
std::vector < int > solve(const std::vector < std::string > & vertices, const std::vector < std::vector < std::string >> & edges) {
std::unordered_map < std::string, std::vector < std::string >> adj;
for (const auto & v : vertices) if (! adj.emplace(v, std::vector < std::string > {
}).second) throw std::invalid_argument("duplicate vertex");
for (const auto & e : edges) {
if (! adj.count(e[0]) || ! adj.count(e[1])) throw std::invalid_argument("unknown endpoint");
adj[e[0]].push_back(e[1]);
adj[e[1]].push_back(e[0]);
}
std::unordered_set < std::string > seen;
std::vector < int > out;
for (const auto & s : vertices) {
if (! seen.insert(s).second) continue;
std::queue < std::string > q;
q.push(s);
int count = 0;
while (! q.empty()) {
auto v = q.front();
q.pop();
count++;
for (const auto & w : adj[v]) if (seen.insert(w).second) q.push(w);
}
out.push_back(count);
}
std::sort(out.begin(), out.end());
return out;
}
문자열 벡터 vertices와 두 문자열 벡터 목록 edges를 solve에 전달하여 정수 벡터를 반환합니다. C++11 이상.
using System;
using System.Collections.Generic;
public class Solution {
public int[] Solve(string[] vertices, string[][] edges) {
var adj = new Dictionary < string, List < string >>();
foreach(string v in vertices) {
if (adj.ContainsKey(v)) throw new ArgumentException("duplicate vertex");
adj.Add(v, new List < string >());
}
foreach(var e in edges) {
if (! adj.ContainsKey(e[0]) || ! adj.ContainsKey(e[1])) throw new ArgumentException("unknown endpoint");
adj[e[0]].Add(e[1]);
adj[e[1]].Add(e[0]);
}
var seen = new HashSet < string >();
var result = new List < int >();
foreach(string s in vertices) {
if (! seen.Add(s)) continue;
var q = new Queue < string >();
q.Enqueue(s);
int count = 0;
while (q.Count > 0) {
string v = q.Dequeue();
count++;
foreach(string w in adj[v]) if (seen.Add(w)) q.Enqueue(w);
}
result.Add(count);
}
result.Sort();
return result.ToArray();
}
}
vertices 문자열 배열과 edges 두 문자열 쌍 배열을 Solve에 전달하여 int[]를 반환합니다.
한 파일로 실행하는 전체 정답
'use strict';
class Graph {
constructor(directed = false) {
this.directed = directed;
this.adj = new Map();
}
addVertex(vertex) {
if (typeof vertex !== 'string') throw new TypeError('vertex must be a string');
if (!this.adj.has(vertex)) this.adj.set(vertex, []);
return this;
}
addEdge(from, to) {
this.addVertex(from);
this.addVertex(to);
this.adj.get(from).push(to);
if (!this.directed) this.adj.get(to).push(from);
return this;
}
hasEdge(from, to) {
return this.adj.get(from)?.includes(to) ?? false;
}
toMatrix() {
const vertices = [...this.adj.keys()];
const indices = new Map(vertices.map((vertex, index) => [vertex, index]));
const matrix = Array.from({ length: vertices.length },
() => new Array(vertices.length).fill(0));
for (const [from, neighbors] of this.adj) {
for (const to of neighbors) matrix[indices.get(from)][indices.get(to)] = 1;
}
return { vertices, matrix };
}
bfs(start) {
if (!this.adj.has(start)) return [];
const visited = new Set([start]);
const queue = [start];
let head = 0;
while (head < queue.length) {
const vertex = queue[head];
head += 1;
for (const next of this.adj.get(vertex)) {
if (visited.has(next)) continue;
visited.add(next);
queue.push(next);
}
}
return queue;
}
}
function solve({ vertices, edges }) {
const graph = new Graph();
for (const vertex of vertices) graph.addVertex(vertex);
if (graph.adj.size !== vertices.length) throw new TypeError('duplicate vertex');
for (const [from, to] of edges) {
if (!graph.adj.has(from) || !graph.adj.has(to)) {
throw new TypeError('edge endpoint is not in vertices');
}
graph.addEdge(from, to);
}
const visited = new Set();
const sizes = [];
for (const start of vertices) {
if (visited.has(start)) continue;
const queue = [start];
visited.add(start);
let head = 0;
while (head < queue.length) {
const vertex = queue[head];
head += 1;
for (const next of graph.adj.get(vertex)) {
if (visited.has(next)) continue;
visited.add(next);
queue.push(next);
}
}
sizes.push(queue.length);
}
return sizes.sort((a, b) => a - b);
}
module.exports = { solve };
if (require.main === module) {
const fs = require('node:fs');
const input = JSON.parse(fs.readFileSync(0, 'utf8').replace(/^\uFEFF/, ''));
process.stdout.write(JSON.stringify(solve(input)) + '\n');
}
정답 해설: 한 BFS가 한 연결 요소를 셉니다
addVertex를 먼저 호출해야 간선에 나오지 않은 정점도 adj에 남습니다. addEdge는 원래 없는 정점을 자동 생성할 수 있는 API지만 이 문제에서는 명시적 목록이 전체 세계이므로 끝점을 확인한 뒤 호출합니다. 자료구조의 편의 기능과 문제의 입력 계약을 구분합니다.
바깥 반복은 정점 목록 전체를 읽습니다. 이미 visited에 있으면 이전 BFS에서 같은 구역으로 센 것이므로 건너뜁니다. 새 정점이면 새 큐에 넣고 이웃을 따라가면서 처음 발견한 정점만 넣습니다. 무방향 그래프에서 이 탐색은 시작점과 연결된 모든 정점, 그리고 그 정점들만 방문합니다.
큐에 넣을 때 visited를 갱신하므로 중복 간선과 자기 루프는 큐 길이를 부풀리지 않습니다. BFS가 끝나면 queue.length가 해당 구역의 정점 수입니다. 큐의 head가 끝까지 이동했어도 배열에 발견된 정점들이 남아 있으므로 별도의 count 없이 이 길이를 사용할 수 있습니다.
| 시작점 | 발견 정점 | 크기 | 바깥 반복의 이후 처리 |
|---|---|---|---|
| a | a,b,c | 3 | b,c는 건너뜀 |
| d | d,e | 2 | e는 건너뜀 |
| f | f | 1 | 자기 루프로 다시 추가하지 않음 |
| 정렬 | — | [1,2,3] | 작은 구역부터 출력 |
전체 탐색 후 숫자 비교 함수 (a,b) => a-b로 크기를 정렬합니다. 기본 sort는 문자열 기준이므로 10과 2 같은 크기에서 잘못된 순서를 만들 수 있습니다. 서로 다른 구역이 같은 크기를 갖는 것은 정상이라 중복 크기를 제거하지 않습니다.
복잡도: 마지막 정렬도 포함합니다
짧은 식별자의 Map/Set 접근과 배열 append를 기대·상환 O(1)로 보는 모델에서 그래프 구축과 전체 BFS는 O(V+E)입니다. 연결 구역 수를 c라고 하면 마지막 정렬 O(c log(c+1))을 더해 총 O(V+E+c log(c+1))입니다. 출력 정렬이 있는데 전체를 무조건 O(V+E)라고 줄이지 않습니다.
인접 리스트는 O(V+E), visited는 O(V), 한 구역의 누적 큐는 최대 O(V), 결과는 O(c)입니다. 입력 JSON과 식별자 문자열 총길이 C를 포함한 전체 공간은 O(V+E+C)입니다. 처리한 큐 항목을 즉시 제거하지 않으므로 큐가 순간 경계 폭만큼의 공간이라는 주장은 이 구현과 맞지 않습니다.
직접 확인할 경계
풀이 직접 실행
본문 풀이에 예제 입력을 넣고, 입력을 바꿔 계산 결과를 비교합니다.
예제 크게 보기 (새 탭)빈 그래프, 정점만 있고 간선 없음, 모든 정점이 한 구역, 중복 간선, 자기 루프, 크기 2와 크기 10인 두 구역을 확인하세요. 작은 무작위 그래프에서는 인접 행렬의 전이 폐쇄로 연결 관계를 구한 결과와 비교하면 BFS 구현과 독립적으로 구역 크기를 검증할 수 있습니다.
함께 연습하고 확인할 자료
Number of Provinces: 행렬로 주어진 연결 관계에서 구역 수를 구하는 외부 연습입니다. 이 글은 정점·간선 목록에서 구역 크기를 출력합니다.
관련 개념 참고: Princeton Algorithms — Undirected Graphs 공식 자료와 문제 페이지는 2026년 9월 10일 확인했습니다. 외부 문제의 지문·예시를 복제하지 않고 이 글의 예제와 창작 문제를 별도로 구성했습니다.
이 문제를 푼 뒤 확인하기
고립 노드와 서로 분리된 여러 구역이 있는 그래프에서 각 구역 크기를 세어 보세요.
본문의 예제와 이 반례를 직접 실행해 예상 출력과 비교하고, 결과가 다르면 입력 조건과 반복 범위를 다시 확인하세요.
글 소개·학습 준비·전체 목차

핵심 요약
모든 정점을 포함한 무방향 그래프의 연결 요소 크기를 오름차순으로 출력합니다. 고립 정점과 중복 간선을 구별하고 전체 그래프를 방문하는 독립 창작 문제입니다.
이 글이 도움이 되었나요?
이어서 공부하기
체크포인트: 해설을 가리고 다시 풀고, 경계 입력을 하나 바꾸어 예상 결과와 실제 결과를 비교하세요. 층별 방문 순서와 최솟값 추출을 추적하고, 다익스트라의 비음수 조건과 오래된 후보를 설명하세요.
다음 추천 · 알고리즘 개념: 다익스트라의 원리: 거리 완화와 오래된 후보 건너뛰기
권장 순서는 학습 가이드와 같습니다. 다른 순서는 접힌 참고 목록에서 확인할 수 있습니다. 읽음 표시는 숙달이나 채점 결과가 아닙니다.
원래 과정 목록 순서 (선택)
읽음 기록
읽음 — / 전체 58개 · 필수 58개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.