
핵심 요약
모든 정점을 포함한 무방향 그래프의 연결 요소 크기를 오름차순으로 출력합니다. 고립 정점과 중복 간선을 구별하고 전체 그래프를 방문하는 독립 창작 문제입니다.
독립 창작 문제
이 문제의 상황·입출력 계약·예시는 이 글을 위해 직접 구성했습니다. 공식 문제를 번역하거나 복제한 지문이 아닙니다. 구현 개념은 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는 모든 구역에서 공유해야 합니다.
정답 코드와 해설 펼치기
한 파일로 실행하는 전체 정답
'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일 확인했습니다. 외부 문제의 지문·예시를 복제하지 않고 이 글의 예제와 창작 문제를 별도로 구성했습니다.
풀이 전에 확인할 순서
- 입력값과 출력값을 한 문장으로 다시 적습니다.
- 반복할 대상과 비교·저장할 값을 정합니다.
- 필요한 자료구조와 시간복잡도를 예상합니다.
- 코드를 보기 전에 손으로 작은 예제를 계산합니다.
힌트: JavaScript 그래프 연습: 연결 구역 크기를 작은 순서로 출력하기
정답 코드를 바로 따라 쓰기보다, 본문에서 값이 갱신되는 조건과 반복 범위를 먼저 찾으세요. 반복 한 번마다 반드시 유지되어야 하는 값이 무엇인지 적으면 풀이의 중심 변수를 고르기 쉽습니다.
테스트 확인
- 가능한 가장 작은 입력
- 같은 값이나 문자가 반복되는 입력
- 정답이 처음 또는 마지막 위치에서 결정되는 입력
- 입력 제한에 가까운 경우의 실행 시간
확인 결과: 본문의 예제뿐 아니라 위 경계 사례에서도 예상값과 실제 출력이 같아야 풀이가 완료됩니다.
이 글이 도움이 되었나요?
코딩테스트 JavaScript 학습 순서
필수 49개 · 전체 49개
읽음 기록 관리
전체 과정 목차 (49개)
- 필수 길잡이 · 코딩테스트 JS 자료구조 로드맵: 배열, 해시, 스택, 투 포인터 순서
- 필수 학습 · 세 수 중 최솟값 JavaScript 조건문 풀이 정리
- 필수 학습 · 삼각형 판별하기 JavaScript 풀이
- 필수 학습 · 연필 개수 JavaScript 풀이
- 필수 학습 · 1부터 N까지 합 출력하기 JavaScript 풀이
- 필수 학습 · 최솟값 구하기 JavaScript 풀이|배열 순회와 비교 갱신 원리
- 필수 학습 · 홀수 JavaScript 풀이: 조건 판별과 결과 처리 정리
- 필수 학습 · 10부제 JavaScript 풀이: 끝자리 비교로 위반 차량 수 세기
- 필수 학습 · A를 #으로 JavaScript 풀이: 문자열 순회와 치환
- 필수 학습 · 문자 찾기 JavaScript 풀이: 문자열 순회로 개수 세기
- 필수 학습 · 대문자 찾기 JavaScript 풀이
- 필수 학습 · 대문자로 통일 JavaScript 풀이
- 필수 학습 · 대소문자 변환 JavaScript 풀이
- 필수 학습 · 일곱 난쟁이 JavaScript 풀이: 두 명을 제외하는 완전탐색
- 필수 학습 · 코딩테스트 JS Map 풀이: 학급 회장 득표수 세기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 올바른 괄호 검증하기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 괄호문자 제거하기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 크레인 인형뽑기 처리법
- 필수 학습 · 코딩테스트 JS 스택 풀이: 후위식 연산 계산하기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 쇠막대기 레이저 절단 개수 세기
- 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 두 정렬 배열 합치기
- 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 공통 원소 추출하기
- 필수 학습 · 코딩테스트 JS 슬라이딩 윈도우 풀이: 최대 매출 구간 합 계산하기
- 필수 학습 · JavaScript 투 포인터: 합이 M인 연속 부분수열 개수
- 필수 학습 · 코딩테스트 JS 해시 풀이: 모든 아나그램 찾기
- 필수 학습 · 가장 긴 문자열 JavaScript 풀이
- 필수 학습 · 가운데 문자 출력 JavaScript 풀이
- 필수 학습 · 중복문자제거 JavaScript 풀이
- 필수 학습 · 코딩테스트 JS 고급: 최소 힙으로 다익스트라 최단 경로 구하기
- 필수 학습 · 코딩테스트 JS Union-Find: 연결 성분 수와 크기 구하기
- 필수 학습 · 코딩테스트 JS Trie: 접두사에 맞는 단어 수 세기
- 필수 학습 · 코딩테스트 JS Fenwick Tree: 값 갱신과 구간 합 처리
- 필수 학습 · 코딩테스트 JS 세그먼트 트리: 단일 대입과 구간 합
- 필수 학습 · 코딩테스트 JS LRU 캐시: 지도 타일 재사용 기록
- 필수 학습 · 코딩테스트 JS AVL 트리: 기준 이상 최솟값 찾기
- 필수 학습 · 코딩테스트 JS 큐: 상담 창구 대기열 명령 처리
- 필수 학습 · 코딩테스트 JS 연결 리스트: 재생 대기 목록 관리
- 필수 학습 · JavaScript 원형 덱 연습: 최근 기록 창과 되돌리기
- 필수 학습 · JavaScript 해시 테이블 연습: 정규화 문자열 빈도와 등장 순서
- 필수 학습 · JavaScript 트리 순회 연습: 깊이별 노드 묶기
- 필수 학습 · JavaScript BST 연습: 닫힌 구간의 중복 키 보고서
- 필수 학습 · JavaScript 최소 힙 연습: 동률 순서를 지키는 작업 스케줄러
- 필수 학습 · JavaScript 그래프 연습: 연결 구역 크기를 작은 순서로 출력하기 현재 글
- 필수 학습 · 중복단어제거 JavaScript 풀이
- 필수 학습 · TypeScript 이진 탐색 연습: 숫자 카드 존재 여부 확인
- 필수 학습 · 큰 수 출력하기 JavaScript 풀이
- 필수 학습 · 보이는 학생 JavaScript 풀이
- 필수 학습 · 가위바위보 JavaScript 풀이
- 필수 학습 · 점수계산 JavaScript 풀이
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.