diff --git a/.github/workflows/c.yml b/.github/workflows/c.yml
index a04e5d47d..0e87030d9 100644
--- a/.github/workflows/c.yml
+++ b/.github/workflows/c.yml
@@ -11,7 +11,9 @@ on:
- "codes/c/**/*.c"
- "codes/c/**/*.h"
- "en/codes/c/**/*.c"
+ - "ko/codes/c/**/*.c"
- "en/codes/c/**/*.h"
+ - "ko/codes/c/**/*.h"
pull_request:
branches: ["main"]
paths:
@@ -19,7 +21,9 @@ on:
- "codes/c/**/*.c"
- "codes/c/**/*.h"
- "en/codes/c/**/*.c"
+ - "ko/codes/c/**/*.c"
- "en/codes/c/**/*.h"
+ - "ko/codes/c/**/*.h"
workflow_dispatch:
jobs:
@@ -40,7 +44,7 @@ jobs:
os: [ubuntu-latest, windows-latest]
build_type: [Release]
c_compiler: [gcc, clang, cl]
- code-dir: ["codes/c", "en/codes/c"]
+ code-dir: ["codes/c", "en/codes/c", "ko/codes/c"]
include:
- os: windows-latest
c_compiler: cl
diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml
index f190299e7..100fc424f 100644
--- a/.github/workflows/cpp.yml
+++ b/.github/workflows/cpp.yml
@@ -11,7 +11,9 @@ on:
- "codes/cpp/**/*.cpp"
- "codes/cpp/**/*.hpp"
- "en/codes/cpp/**/*.cpp"
+ - "ko/codes/cpp/**/*.cpp"
- "en/codes/cpp/**/*.hpp"
+ - "ko/codes/cpp/**/*.hpp"
pull_request:
branches: ["main"]
paths:
@@ -19,7 +21,9 @@ on:
- "codes/cpp/**/*.cpp"
- "codes/cpp/**/*.hpp"
- "en/codes/cpp/**/*.cpp"
+ - "ko/codes/cpp/**/*.cpp"
- "en/codes/cpp/**/*.hpp"
+ - "ko/codes/cpp/**/*.hpp"
workflow_dispatch:
jobs:
@@ -40,7 +44,7 @@ jobs:
os: [ubuntu-latest, windows-latest]
build_type: [Release]
c_compiler: [gcc, clang, cl]
- code-dir: ["codes/cpp", "en/codes/cpp"]
+ code-dir: ["codes/cpp", "en/codes/cpp", "ko/codes/cpp"]
include:
- os: windows-latest
c_compiler: cl
diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml
index 027f8a662..e3fa4cffc 100644
--- a/.github/workflows/dart.yml
+++ b/.github/workflows/dart.yml
@@ -9,12 +9,14 @@ on:
- ".github/workflows/dart.yml"
- "codes/dart/**/*.dart"
- "en/codes/dart/**/*.dart"
+ - "ko/codes/dart/**/*.dart"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/dart.yml"
- "codes/dart/**/*.dart"
- "en/codes/dart/**/*.dart"
+ - "ko/codes/dart/**/*.dart"
workflow_dispatch:
permissions:
@@ -28,7 +30,7 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
dart-sdk: [stable]
- code-dir: ["codes/dart", "en/codes/dart"]
+ code-dir: ["codes/dart", "en/codes/dart", "ko/codes/dart"]
steps:
- uses: actions/checkout@v6
- name: Set up Dart ${{ matrix.dart-sdk }}
@@ -39,5 +41,11 @@ jobs:
run: dart format ${{ matrix.code-dir }}
- name: Run analyze
run: dart analyze ${{ matrix.code-dir }}
- - name: Run build
- run: dart ${{ matrix.code-dir }}/build.dart
+ - name: Run chapter examples
+ shell: bash
+ run: |
+ status=0
+ for file in "${{ matrix.code-dir }}"/chapter_*/*.dart; do
+ dart "$file" || status=1
+ done
+ exit "$status"
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index ee3a23aa0..18acbafe3 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -1,46 +1,48 @@
-# This workflow will build a .NET project
-# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
-
-name: .NET
-
-on:
- push:
- branches: ["main"]
- paths:
- - ".github/workflows/dotnet.yml"
- - "codes/csharp/**/*.cs"
- - "en/codes/csharp/**/*.cs"
- pull_request:
- branches: ["main"]
- paths:
- - ".github/workflows/dotnet.yml"
- - "codes/csharp/**/*.cs"
- - "en/codes/csharp/**/*.cs"
- workflow_dispatch:
-
-jobs:
- build:
- name: .NET ${{ matrix.dotnet-version }} on ${{ matrix.os }}
- runs-on: ${{ matrix.os }}
- defaults:
- run:
- working-directory: ${{ matrix.code-dir }}
- strategy:
- matrix:
- os: [ubuntu-latest, macos-latest, windows-latest]
- dotnet-version: ["8.0.x"]
- code-dir: ["codes/csharp", "en/codes/csharp"]
-
- steps:
- - uses: actions/checkout@v6
-
- - name: Setup .NET ${{ matrix.dotnet-version }}
- uses: actions/setup-dotnet@v3
- with:
- dotnet-version: ${{ matrix.dotnet-version }}
- - name: Restore dependencies
- run: dotnet restore hello-algo.csproj
- - name: Build
- run: dotnet build --no-restore hello-algo.csproj
- - name: Test with dotnet
- run: dotnet test hello-algo.csproj
+# This workflow will build a .NET project
+# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
+
+name: .NET
+
+on:
+ push:
+ branches: ["main"]
+ paths:
+ - ".github/workflows/dotnet.yml"
+ - "codes/csharp/**/*.cs"
+ - "en/codes/csharp/**/*.cs"
+ - "ko/codes/csharp/**/*.cs"
+ pull_request:
+ branches: ["main"]
+ paths:
+ - ".github/workflows/dotnet.yml"
+ - "codes/csharp/**/*.cs"
+ - "en/codes/csharp/**/*.cs"
+ - "ko/codes/csharp/**/*.cs"
+ workflow_dispatch:
+
+jobs:
+ build:
+ name: .NET ${{ matrix.dotnet-version }} on ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+ defaults:
+ run:
+ working-directory: ${{ matrix.code-dir }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ dotnet-version: ["8.0.x"]
+ code-dir: ["codes/csharp", "en/codes/csharp", "ko/codes/csharp"]
+
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Setup .NET ${{ matrix.dotnet-version }}
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: ${{ matrix.dotnet-version }}
+ - name: Restore dependencies
+ run: dotnet restore hello-algo.csproj
+ - name: Build
+ run: dotnet build --no-restore hello-algo.csproj
+ - name: Test with dotnet
+ run: dotnet test hello-algo.csproj
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 358fe9278..89408adbd 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -7,12 +7,14 @@ on:
- ".github/workflows/go.yml"
- "codes/go/**/*.go"
- "en/codes/go/**/*.go"
+ - "ko/codes/go/**/*.go"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/go.yml"
- "codes/go/**/*.go"
- "en/codes/go/**/*.go"
+ - "ko/codes/go/**/*.go"
workflow_dispatch:
jobs:
@@ -26,7 +28,7 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go-version: ["1.19.x"]
- code-dir: ["codes/go", "en/codes/go"]
+ code-dir: ["codes/go", "en/codes/go", "ko/codes/go"]
steps:
- uses: actions/checkout@v6
diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml
index 0e0caaec0..2867caab7 100644
--- a/.github/workflows/java.yml
+++ b/.github/workflows/java.yml
@@ -10,12 +10,14 @@ on:
- ".github/workflows/java.yml"
- "codes/java/**/*.java"
- "en/codes/java/**/*.java"
+ - "ko/codes/java/**/*.java"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/java.yml"
- "codes/java/**/*.java"
- "en/codes/java/**/*.java"
+ - "ko/codes/java/**/*.java"
workflow_dispatch:
jobs:
@@ -24,7 +26,7 @@ jobs:
strategy:
matrix:
java: ["11", "17"]
- code-dir: ["codes/java", "en/codes/java"]
+ code-dir: ["codes/java", "en/codes/java", "ko/codes/java"]
name: Java ${{ matrix.Java }} sample
steps:
- uses: actions/checkout@v6
diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml
index 27171e373..f3fc36e1e 100644
--- a/.github/workflows/javascript.yml
+++ b/.github/workflows/javascript.yml
@@ -7,12 +7,14 @@ on:
- ".github/workflows/javascript.yml"
- "codes/javascript/**/*.js"
- "en/codes/javascript/**/*.js"
+ - "ko/codes/javascript/**/*.js"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/javascript.yml"
- "codes/javascript/**/*.js"
- "en/codes/javascript/**/*.js"
+ - "ko/codes/javascript/**/*.js"
workflow_dispatch:
jobs:
@@ -21,7 +23,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
- code-dir: ["codes/javascript", "en/codes/javascript"]
+ code-dir: ["codes/javascript", "en/codes/javascript", "ko/codes/javascript"]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml
index 9a751d69f..253202442 100644
--- a/.github/workflows/kotlin.yml
+++ b/.github/workflows/kotlin.yml
@@ -7,12 +7,14 @@ on:
- ".github/workflows/kotlin.yml"
- "codes/kotlin/**/*.kt"
- "en/codes/kotlin/**/*.kt"
+ - "ko/codes/kotlin/**/*.kt"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/kotlin.yml"
- "codes/kotlin/**/*.kt"
- "en/codes/kotlin/**/*.kt"
+ - "ko/codes/kotlin/**/*.kt"
workflow_dispatch:
jobs:
@@ -21,7 +23,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
- code-dir: ["codes/kotlin", "en/codes/kotlin"]
+ code-dir: ["codes/kotlin", "en/codes/kotlin", "ko/codes/kotlin"]
name: Kotlin on ${{ matrix.os }}
steps:
diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml
index dc9ce6e53..ef955971d 100644
--- a/.github/workflows/python.yml
+++ b/.github/workflows/python.yml
@@ -10,12 +10,14 @@ on:
- ".github/workflows/python.yml"
- "codes/python/**/*.py"
- "en/codes/python/**/*.py"
+ - "ko/codes/python/**/*.py"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/python.yml"
- "codes/python/**/*.py"
- "en/codes/python/**/*.py"
+ - "ko/codes/python/**/*.py"
workflow_dispatch:
permissions:
@@ -25,11 +27,13 @@ jobs:
build:
name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
+ env:
+ PYTHONIOENCODING: utf-8
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.10"]
- code-dir: ["codes/python", "en/codes/python"]
+ code-dir: ["codes/python", "en/codes/python", "ko/codes/python"]
steps:
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml
index de7f0ca93..8375aa057 100644
--- a/.github/workflows/ruby.yml
+++ b/.github/workflows/ruby.yml
@@ -12,12 +12,14 @@ on:
- ".github/workflows/ruby.yml"
- "codes/ruby/**/*.rb"
- "en/codes/ruby/**/*.rb"
+ - "ko/codes/ruby/**/*.rb"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/ruby.yml"
- "codes/ruby/**/*.rb"
- "en/codes/ruby/**/*.rb"
+ - "ko/codes/ruby/**/*.rb"
workflow_dispatch:
permissions:
@@ -31,7 +33,7 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
ruby-version: ["3.3"]
- code-dir: ["codes/ruby", "en/codes/ruby"]
+ code-dir: ["codes/ruby", "en/codes/ruby", "ko/codes/ruby"]
steps:
- uses: actions/checkout@v6
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 3c4ed8317..a9f9b6781 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -8,7 +8,9 @@ on:
- "codes/rust/**/*.rs"
- "codes/rust/Cargo.toml"
- "en/codes/rust/**/*.rs"
+ - "ko/codes/rust/**/*.rs"
- "en/codes/rust/Cargo.toml"
+ - "ko/codes/rust/Cargo.toml"
pull_request:
branches: ["main"]
paths:
@@ -16,7 +18,9 @@ on:
- "codes/rust/**/*.rs"
- "codes/rust/Cargo.toml"
- "en/codes/rust/**/*.rs"
+ - "ko/codes/rust/**/*.rs"
- "en/codes/rust/Cargo.toml"
+ - "ko/codes/rust/Cargo.toml"
jobs:
build:
@@ -25,7 +29,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
- code-dir: ["codes/rust", "en/codes/rust"]
+ code-dir: ["codes/rust", "en/codes/rust", "ko/codes/rust"]
steps:
- uses: brndnmtthws/rust-action-rustup@913349b802fe843511093e8c2014c94d8168c6b6 # v1
diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml
index 26f74e659..a114989bc 100644
--- a/.github/workflows/swift.yml
+++ b/.github/workflows/swift.yml
@@ -10,12 +10,14 @@ on:
- ".github/workflows/swift.yml"
- "codes/swift/**/*.swift"
- "en/codes/swift/**/*.swift"
+ - "ko/codes/swift/**/*.swift"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/swift.yml"
- "codes/swift/**/*.swift"
- "en/codes/swift/**/*.swift"
+ - "ko/codes/swift/**/*.swift"
workflow_dispatch:
jobs:
@@ -25,7 +27,7 @@ jobs:
strategy:
matrix:
os: ["ubuntu-22.04", "macos-14"]
- code-dir: ["codes/swift", "en/codes/swift"]
+ code-dir: ["codes/swift", "en/codes/swift", "ko/codes/swift"]
steps:
- uses: actions/checkout@v6
- name: Build
diff --git a/.github/workflows/typescript.yml b/.github/workflows/typescript.yml
index 4a3605a80..3d632525f 100644
--- a/.github/workflows/typescript.yml
+++ b/.github/workflows/typescript.yml
@@ -7,12 +7,14 @@ on:
- ".github/workflows/typescript.yml"
- "codes/typescript/**/*.ts"
- "en/codes/typescript/**/*.ts"
+ - "ko/codes/typescript/**/*.ts"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/typescript.yml"
- "codes/typescript/**/*.ts"
- "en/codes/typescript/**/*.ts"
+ - "ko/codes/typescript/**/*.ts"
workflow_dispatch:
jobs:
@@ -21,7 +23,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
- code-dir: ["codes/typescript", "en/codes/typescript"]
+ code-dir: ["codes/typescript", "en/codes/typescript", "ko/codes/typescript"]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
diff --git a/.github/workflows/zig.yml b/.github/workflows/zig.yml
index 3d038d24d..84e8b0ef9 100644
--- a/.github/workflows/zig.yml
+++ b/.github/workflows/zig.yml
@@ -6,13 +6,17 @@ on:
paths:
- ".github/workflows/zig.yml"
- "codes/zig/**/*.zig"
+ - "ko/codes/zig/**/*.zig"
- "codes/zig/build.zig"
+ - "ko/codes/zig/build.zig"
pull_request:
branches: ["main"]
paths:
- ".github/workflows/zig.yml"
- "codes/zig/**/*.zig"
+ - "ko/codes/zig/**/*.zig"
- "codes/zig/build.zig"
+ - "ko/codes/zig/build.zig"
workflow_dispatch:
jobs:
@@ -25,6 +29,7 @@ jobs:
matrix:
# Keep the macOS SDK compatible with Zig 0.14.1.
os: [ubuntu-latest, macos-14, windows-latest]
+ code-dir: ["codes/zig", "ko/codes/zig"]
steps:
- uses: actions/checkout@v4
@@ -34,9 +39,9 @@ jobs:
version: "0.14.1"
- name: Build
- working-directory: codes/zig
+ working-directory: ${{ matrix.code-dir }}
run: zig build
- name: Test
- working-directory: codes/zig
+ working-directory: ${{ matrix.code-dir }}
run: zig build test
diff --git a/Dockerfile b/Dockerfile
index e2b330433..a0f1bc1a6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -33,6 +33,10 @@ COPY ru/docs ./build/ru/docs
COPY ru/mkdocs.yml ./ru/mkdocs.yml
RUN mkdocs build -f ./ru/mkdocs.yml
+COPY ko/docs ./build/ko/docs
+COPY ko/mkdocs.yml ./ko/mkdocs.yml
+RUN mkdocs build -f ./ko/mkdocs.yml
+
WORKDIR /hello-algo/site
EXPOSE 8000
CMD ["python", "-m", "http.server", "8000"]
diff --git a/README.md b/README.md
index 614bf6211..7086aa610 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,8 @@
日本語
|
Русский
+ |
+ 한국어
## 关于本书
diff --git a/en/README.md b/en/README.md
index dc2e66110..ce941b7cc 100644
--- a/en/README.md
+++ b/en/README.md
@@ -47,6 +47,8 @@
日本語
|
Русский
+ |
+ 한국어
## The book
diff --git a/ja/README.md b/ja/README.md
index 6f7109b2b..c5b4ed718 100644
--- a/ja/README.md
+++ b/ja/README.md
@@ -47,6 +47,8 @@
日本語
|
Русский
+ |
+ 한국어
## この本について
diff --git a/ko/README.md b/ko/README.md
new file mode 100644
index 000000000..65c0046ee
--- /dev/null
+++ b/ko/README.md
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+ 애니메이션으로 설명하고 클릭 한 번으로 실행하는 자료 구조와 알고리즘 입문서
+
+
+
+
+ 온라인으로 읽기
+
+ PDF/EPUB 제공 언어 확인
+
+
+한국어판은 온라인으로 읽을 수 있다. PDF/EPUB의 제공 언어는 릴리스 페이지에서 확인할 수 있다.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 简体中文
+ |
+ 繁體中文
+ |
+ English
+ |
+ 日本語
+ |
+ Русский
+ |
+ 한국어
+
+
+## 이 책에 대하여
+
+이 프로젝트는 오픈 소스로 공개되는 무료 자료 구조와 알고리즘 입문서를 만드는 것을 목표로 한다. 처음 배우는 사람도 쉽게 접근할 수 있는 책을 지향한다.
+
+- 책 전체에 애니메이션 그림을 사용하여 내용을 명확하고 이해하기 쉽게 설명한다. 난이도를 완만하게 높여 가며 초보자가 자료 구조와 알고리즘의 지식 지도를 탐험하도록 안내한다.
+- 소스 코드를 클릭 한 번으로 실행할 수 있다. 독자가 실습을 통해 프로그래밍 능력을 키우고 알고리즘의 동작 원리와 자료 구조의 내부 구현을 이해하도록 돕는다.
+- 독자들이 서로 도우며 공부하기를 권장한다. 댓글에서 질문하고 의견을 나누며 함께 성장할 수 있다.
+
+이 책이 도움이 되었다면 페이지 오른쪽 위의 Star :star:를 눌러 응원해 주기 바란다. 감사드린다!
+
+## 추천의 말
+
+> “쉽게 읽을 수 있는 자료 구조와 알고리즘 입문서다. 직접 실습하고 생각하며 배우도록 안내한다. 알고리즘 입문자에게 적극 추천한다.”
+>
+> **—— 덩쥔후이, 칭화대학교 컴퓨터학과 교수**
+
+> “내가 자료 구조와 알고리즘을 배울 때 『Hello 알고리즘』이 있었다면 공부가 열 배는 쉬웠을 것이다!”
+>
+> **—— 리무, 아마존 수석 과학자(Senior Principal Scientist)**
+
+## 기여
+
+이 오픈 소스 도서는 계속 갱신되고 있다. 프로젝트에 참여하여 독자에게 더 좋은 학습 내용을 제공하는 일에 함께해 주기 바란다.
+
+- [내용 수정](https://www.hello-algo.com/ko/chapter_appendix/contribution/): 문법 오류, 빠진 내용, 모호한 표현, 끊어진 링크, 코드 버그 등을 수정하거나 댓글로 알려 주기 바란다.
+- [코드 이식](https://github.com/krahets/hello-algo/issues/15): 다양한 프로그래밍 언어의 코드 기여를 기다린다. 현재 Python, Java, C++, Go, JavaScript 등 12개 언어를 지원한다.
+- 번역 검토: 더 많은 독자가 모국어로 자료 구조와 알고리즘을 공부할 수 있도록 다국어 번역과 교정에 참여해 주기 바란다.
+
+소중한 의견과 제안을 기다린다. 궁금한 점이 있으면 Issue를 만들거나 위챗에서 `krahets-jyd`로 연락하기 바란다.
+
+이 책을 더 좋은 책으로 만들어 준 모든 집필 참여자의 아낌없는 기여에 감사드린다. 참여자는 다음과 같다:
+
+
+
+
+
+
+
+## 라이선스
+
+이 저장소의 글, 코드, 그림, 사진 및 영상에는 [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) 라이선스가 적용된다.
diff --git a/ko/codes/Dockerfile b/ko/codes/Dockerfile
new file mode 100644
index 000000000..ed986beda
--- /dev/null
+++ b/ko/codes/Dockerfile
@@ -0,0 +1,35 @@
+FROM ubuntu:latest
+
+# Aliyun의 Ubuntu 이미지를 사용한다
+RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
+ sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
+ sed -i 's/ports.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list
+
+RUN apt-get update && apt-get install -y wget
+
+# 프로그래밍 언어 실행 환경을 설치한다
+ARG LANGS
+RUN for LANG in $LANGS; do \
+ case $LANG in \
+ python) \
+ apt-get install -y python3.10 && \
+ update-alternatives --install /usr/bin/python python /usr/bin/python3.10 1 ;; \
+ cpp) \
+ apt-get install -y g++ gdb ;; \
+ java) \
+ apt-get install -y openjdk-17-jdk ;; \
+ csharp) \
+ wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && \
+ dpkg -i packages-microsoft-prod.deb && \
+ apt-get update && \
+ apt-get install -y dotnet-sdk-8.0 ;; \
+ # 추가 언어...
+ *) \
+ echo "경고: $LANG 설치 절차가 없다" ;; \
+ esac \
+ done
+
+WORKDIR /codes
+COPY ./ ./
+
+CMD ["/bin/bash"]
diff --git a/ko/codes/c/.gitignore b/ko/codes/c/.gitignore
new file mode 100644
index 000000000..fcd76572a
--- /dev/null
+++ b/ko/codes/c/.gitignore
@@ -0,0 +1,9 @@
+# 모든 항목을 무시한다
+*
+# 확장자가 있는 모든 항목을 무시 대상에서 제외한다
+!*.*
+# 모든 디렉터리를 무시 대상에서 제외한다
+!*/
+*.dSYM/
+
+build/
diff --git a/ko/codes/c/CMakeLists.txt b/ko/codes/c/CMakeLists.txt
new file mode 100644
index 000000000..bb5f8f6a9
--- /dev/null
+++ b/ko/codes/c/CMakeLists.txt
@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 3.10)
+project(hello_algo C)
+
+set(CMAKE_C_STANDARD 11)
+
+include_directories(./include)
+
+add_subdirectory(chapter_computational_complexity)
+add_subdirectory(chapter_array_and_linkedlist)
+add_subdirectory(chapter_stack_and_queue)
+add_subdirectory(chapter_hashing)
+add_subdirectory(chapter_tree)
+add_subdirectory(chapter_heap)
+add_subdirectory(chapter_graph)
+add_subdirectory(chapter_searching)
+add_subdirectory(chapter_sorting)
+add_subdirectory(chapter_divide_and_conquer)
+add_subdirectory(chapter_backtracking)
+add_subdirectory(chapter_dynamic_programming)
+add_subdirectory(chapter_greedy)
diff --git a/ko/codes/c/chapter_array_and_linkedlist/CMakeLists.txt b/ko/codes/c/chapter_array_and_linkedlist/CMakeLists.txt
new file mode 100644
index 000000000..29677a0be
--- /dev/null
+++ b/ko/codes/c/chapter_array_and_linkedlist/CMakeLists.txt
@@ -0,0 +1,3 @@
+add_executable(array array.c)
+add_executable(linked_list linked_list.c)
+add_executable(my_list my_list.c)
\ No newline at end of file
diff --git a/ko/codes/c/chapter_array_and_linkedlist/array.c b/ko/codes/c/chapter_array_and_linkedlist/array.c
new file mode 100644
index 000000000..3b7a20145
--- /dev/null
+++ b/ko/codes/c/chapter_array_and_linkedlist/array.c
@@ -0,0 +1,117 @@
+/**
+ * File: array.c
+ * Created Time: 2022-12-20
+ * Author: MolDuM (moldum@163.com)
+ */
+
+#include "../utils/common.h"
+
+/* 원소에 임의 접근 */
+int randomAccess(int *nums, int size) {
+ // 구간 [0, size)에서 무작위로 수 하나 선택
+ int randomIndex = rand() % size;
+ // 무작위 원소를 가져와 반환한다
+ int randomNum = nums[randomIndex];
+ return randomNum;
+}
+
+/* 배열 길이 확장 */
+int *extend(int *nums, int size, int enlarge) {
+ // 길이를 확장한 배열을 초기화한다
+ int *res = (int *)malloc(sizeof(int) * (size + enlarge));
+ // 원래 배열의 모든 원소를 새 배열에 복사한다
+ for (int i = 0; i < size; i++) {
+ res[i] = nums[i];
+ }
+ // 확장한 공간 초기화
+ for (int i = size; i < size + enlarge; i++) {
+ res[i] = 0;
+ }
+ // 확장한 새 배열을 반환한다
+ return res;
+}
+
+/* 배열의 인덱스 index에 원소 num 삽입 */
+void insert(int *nums, int size, int num, int index) {
+ // 인덱스 index와 그 뒤에 있는 모든 원소를 한 칸씩 뒤로 이동한다
+ for (int i = size - 1; i > index; i--) {
+ nums[i] = nums[i - 1];
+ }
+ // index 위치의 원소에 num을 대입한다
+ nums[index] = num;
+}
+
+/* 인덱스 index에 있는 원소 삭제 */
+// 주의: stdio.h가 remove라는 이름을 사용한다
+void removeItem(int *nums, int size, int index) {
+ // 인덱스 index 뒤에 있는 모든 원소를 한 칸씩 앞으로 이동한다
+ for (int i = index; i < size - 1; i++) {
+ nums[i] = nums[i + 1];
+ }
+}
+
+/* 배열 순회 */
+void traverse(int *nums, int size) {
+ int count = 0;
+ // 인덱스를 통해 배열을 순회한다
+ for (int i = 0; i < size; i++) {
+ count += nums[i];
+ }
+}
+
+/* 배열에서 지정한 원소 탐색 */
+int find(int *nums, int size, int target) {
+ for (int i = 0; i < size; i++) {
+ if (nums[i] == target)
+ return i;
+ }
+ return -1;
+}
+
+/* 실행 코드 */
+int main() {
+ /* 난수 시드 초기화(한 번만 실행하면 된다) */
+ srand((unsigned)time(NULL));
+
+ /* 배열 초기화 */
+ int size = 5;
+ int arr[5];
+ printf("배열 arr = ");
+ printArray(arr, size);
+
+ int nums[] = {1, 3, 2, 5, 4};
+ printf("배열 nums = ");
+ printArray(nums, size);
+
+ /* 임의 접근 */
+ int randomNum = randomAccess(nums, size);
+ printf("nums에서 무작위로 가져온 원소: %d", randomNum);
+
+ /* 길이 확장 */
+ int enlarge = 3;
+ int *res = extend(nums, size, enlarge);
+ size += enlarge;
+ printf("배열 길이를 8로 확장한 후 nums = ");
+ printArray(res, size);
+
+ /* 원소 삽입 */
+ insert(res, size, 6, 3);
+ printf("인덱스 3에 숫자 6을 삽입한 후 nums = ");
+ printArray(res, size);
+
+ /* 원소 삭제 */
+ removeItem(res, size, 2);
+ printf("인덱스 2의 원소를 삭제한 후 nums = ");
+ printArray(res, size);
+
+ /* 배열 순회 */
+ traverse(res, size);
+
+ /* 원소 탐색 */
+ int index = find(res, size, 3);
+ printf("res에서 원소 3을 찾아 얻은 인덱스 = %d\n", index);
+
+ /* 메모리 해제 */
+ free(res);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_array_and_linkedlist/linked_list.c b/ko/codes/c/chapter_array_and_linkedlist/linked_list.c
new file mode 100644
index 000000000..c0699c2ad
--- /dev/null
+++ b/ko/codes/c/chapter_array_and_linkedlist/linked_list.c
@@ -0,0 +1,89 @@
+/**
+ * File: linked_list.c
+ * Created Time: 2023-01-12
+ * Author: Zero (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 연결 리스트의 노드 n0 뒤에 노드 P 삽입 */
+void insert(ListNode *n0, ListNode *P) {
+ ListNode *n1 = n0->next;
+ P->next = n1;
+ n0->next = P;
+}
+
+/* 연결 리스트의 노드 n0 바로 뒤에 있는 노드 삭제 */
+// 주의: stdio.h가 remove라는 이름을 사용한다
+void removeItem(ListNode *n0) {
+ if (!n0->next)
+ return;
+ // n0 -> P -> n1
+ ListNode *P = n0->next;
+ ListNode *n1 = P->next;
+ n0->next = n1;
+ // 메모리 해제
+ free(P);
+}
+
+/* 연결 리스트에서 인덱스가 index인 노드에 접근 */
+ListNode *access(ListNode *head, int index) {
+ for (int i = 0; i < index; i++) {
+ if (head == NULL)
+ return NULL;
+ head = head->next;
+ }
+ return head;
+}
+
+/* 연결 리스트에서 값이 target인 첫 번째 노드 탐색 */
+int find(ListNode *head, int target) {
+ int index = 0;
+ while (head) {
+ if (head->val == target)
+ return index;
+ head = head->next;
+ index++;
+ }
+ return -1;
+}
+
+/* 실행 코드 */
+int main() {
+ /* 연결 리스트 초기화 */
+ // 각 노드를 초기화한다
+ ListNode *n0 = newListNode(1);
+ ListNode *n1 = newListNode(3);
+ ListNode *n2 = newListNode(2);
+ ListNode *n3 = newListNode(5);
+ ListNode *n4 = newListNode(4);
+ // 노드 사이의 참조를 설정한다
+ n0->next = n1;
+ n1->next = n2;
+ n2->next = n3;
+ n3->next = n4;
+ printf("초기화한 연결 리스트\r\n");
+ printLinkedList(n0);
+
+ /* 노드 삽입 */
+ insert(n0, newListNode(0));
+ printf("노드를 삽입한 뒤의 연결 리스트\r\n");
+ printLinkedList(n0);
+
+ /* 노드 삭제 */
+ removeItem(n0);
+ printf("노드를 삭제한 뒤의 연결 리스트\r\n");
+ printLinkedList(n0);
+
+ /* 노드 접근 */
+ ListNode *node = access(n0, 3);
+ printf("연결 리스트에서 인덱스 3의 노드 값 = %d\r\n", node->val);
+
+ /* 노드 탐색 */
+ int index = find(n0, 2);
+ printf("연결 리스트에서 값이 2인 노드의 인덱스 = %d\r\n", index);
+
+ // 메모리 해제
+ freeMemoryLinkedList(n0);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_array_and_linkedlist/my_list.c b/ko/codes/c/chapter_array_and_linkedlist/my_list.c
new file mode 100644
index 000000000..e20f8a5de
--- /dev/null
+++ b/ko/codes/c/chapter_array_and_linkedlist/my_list.c
@@ -0,0 +1,163 @@
+/**
+ * File: my_list.c
+ * Created Time: 2023-01-12
+ * Author: Zero (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 리스트 클래스 */
+typedef struct {
+ int *arr; // 배열(리스트 원소 저장)
+ int capacity; // 리스트 용량
+ int size; // 리스트 크기
+ int extendRatio; // 리스트 용량을 확장할 때마다 적용하는 배수
+} MyList;
+
+void extendCapacity(MyList *nums);
+
+/* 생성자 */
+MyList *newMyList() {
+ MyList *nums = malloc(sizeof(MyList));
+ nums->capacity = 10;
+ nums->arr = malloc(sizeof(int) * nums->capacity);
+ nums->size = 0;
+ nums->extendRatio = 2;
+ return nums;
+}
+
+/* 소멸자 */
+void delMyList(MyList *nums) {
+ free(nums->arr);
+ free(nums);
+}
+
+/* 리스트 길이 구하기 */
+int size(MyList *nums) {
+ return nums->size;
+}
+
+/* 리스트 용량 반환 */
+int capacity(MyList *nums) {
+ return nums->capacity;
+}
+
+/* 원소 접근 */
+int get(MyList *nums, int index) {
+ assert(index >= 0 && index < nums->size);
+ return nums->arr[index];
+}
+
+/* 원소 갱신 */
+void set(MyList *nums, int index, int num) {
+ assert(index >= 0 && index < nums->size);
+ nums->arr[index] = num;
+}
+
+/* 맨 뒤에 원소 추가 */
+void add(MyList *nums, int num) {
+ if (size(nums) == capacity(nums)) {
+ extendCapacity(nums); // 용량 확장
+ }
+ nums->arr[size(nums)] = num;
+ nums->size++;
+}
+
+/* 중간에 원소 삽입 */
+void insert(MyList *nums, int index, int num) {
+ assert(index >= 0 && index < size(nums));
+ // 원소 개수가 용량을 초과하면 용량 확장을 수행한다
+ if (size(nums) == capacity(nums)) {
+ extendCapacity(nums); // 용량 확장
+ }
+ for (int i = size(nums); i > index; --i) {
+ nums->arr[i] = nums->arr[i - 1];
+ }
+ nums->arr[index] = num;
+ nums->size++;
+}
+
+/* 원소 삭제 */
+// 주의: stdio.h가 remove라는 이름을 사용한다
+int removeItem(MyList *nums, int index) {
+ assert(index >= 0 && index < size(nums));
+ int num = nums->arr[index];
+ for (int i = index; i < size(nums) - 1; i++) {
+ nums->arr[i] = nums->arr[i + 1];
+ }
+ nums->size--;
+ return num;
+}
+
+/* 리스트 용량 확장 */
+void extendCapacity(MyList *nums) {
+ // 먼저 공간 할당
+ int newCapacity = capacity(nums) * nums->extendRatio;
+ int *extend = (int *)malloc(sizeof(int) * newCapacity);
+ int *temp = nums->arr;
+
+ // 기존 데이터를 새 공간에 복사
+ for (int i = 0; i < size(nums); i++)
+ extend[i] = nums->arr[i];
+
+ // 기존 데이터 공간 해제
+ free(temp);
+
+ // 새 데이터로 갱신
+ nums->arr = extend;
+ nums->capacity = newCapacity;
+}
+
+/* 출력을 위해 리스트를 Array로 변환 */
+int *toArray(MyList *nums) {
+ return nums->arr;
+}
+
+/* 실행 코드 */
+int main() {
+ /* 리스트 초기화 */
+ MyList *nums = newMyList();
+ /* 맨 뒤에 원소 추가 */
+ add(nums, 1);
+ add(nums, 3);
+ add(nums, 2);
+ add(nums, 5);
+ add(nums, 4);
+ printf("리스트 nums = ");
+ printArray(toArray(nums), size(nums));
+ printf("용량 = %d, 길이 = %d\n", capacity(nums), size(nums));
+
+ /* 중간에 원소 삽입 */
+ insert(nums, 3, 6);
+ printf("인덱스 3에 숫자 6을 삽입한 후 nums = ");
+ printArray(toArray(nums), size(nums));
+
+ /* 원소 삭제 */
+ removeItem(nums, 3);
+ printf("인덱스 3의 원소를 삭제한 후 nums = ");
+ printArray(toArray(nums), size(nums));
+
+ /* 원소 접근 */
+ int num = get(nums, 1);
+ printf("인덱스 1의 원소에 접근하여 얻은 num = %d\n", num);
+
+ /* 원소 갱신 */
+ set(nums, 1, 0);
+ printf("인덱스 1의 원소를 0으로 갱신한 후 nums = ");
+ printArray(toArray(nums), size(nums));
+
+ /* 용량 확장 동작 테스트 */
+ for (int i = 0; i < 10; i++) {
+ // i = 5일 때 리스트 길이가 용량을 초과하므로 용량 확장이 수행된다
+ add(nums, i);
+ }
+
+ printf("용량을 확장한 후 리스트 nums = ");
+ printArray(toArray(nums), size(nums));
+ printf("용량 = %d, 길이 = %d\n", capacity(nums), size(nums));
+
+ /* 할당한 메모리 해제 */
+ delMyList(nums);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_backtracking/CMakeLists.txt b/ko/codes/c/chapter_backtracking/CMakeLists.txt
new file mode 100644
index 000000000..70161b6dd
--- /dev/null
+++ b/ko/codes/c/chapter_backtracking/CMakeLists.txt
@@ -0,0 +1,10 @@
+add_executable(permutations_i permutations_i.c)
+add_executable(permutations_ii permutations_ii.c)
+add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.c)
+add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.c)
+add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.c)
+add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.c)
+add_executable(subset_sum_i_naive subset_sum_i_naive.c)
+add_executable(subset_sum_i subset_sum_i.c)
+add_executable(subset_sum_ii subset_sum_ii.c)
+add_executable(n_queens n_queens.c)
\ No newline at end of file
diff --git a/ko/codes/c/chapter_backtracking/n_queens.c b/ko/codes/c/chapter_backtracking/n_queens.c
new file mode 100644
index 000000000..a1960527f
--- /dev/null
+++ b/ko/codes/c/chapter_backtracking/n_queens.c
@@ -0,0 +1,95 @@
+/**
+ * File : n_queens.c
+ * Created Time: 2023-09-25
+ * Author : lucas (superrat6@gmail.com)
+ */
+
+#include "../utils/common.h"
+
+#define MAX_SIZE 100
+
+/* 백트래킹 알고리즘: n-퀸 */
+void backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE],
+ bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) {
+ // 모든 행에 배치했으면 해를 기록한다
+ if (row == n) {
+ res[*resSize] = (char **)malloc(sizeof(char *) * n);
+ for (int i = 0; i < n; ++i) {
+ res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1));
+ strcpy(res[*resSize][i], state[i]);
+ }
+ (*resSize)++;
+ return;
+ }
+ // 모든 열을 순회한다
+ for (int col = 0; col < n; col++) {
+ // 해당 칸이 속한 주대각선과 부대각선을 계산한다
+ int diag1 = row - col + n - 1;
+ int diag2 = row + col;
+ // 가지치기: 해당 칸이 속한 열, 주대각선, 부대각선에 퀸이 있으면 안 된다
+ if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {
+ // 시도: 해당 칸에 퀸을 놓는다
+ state[row][col] = 'Q';
+ cols[col] = diags1[diag1] = diags2[diag2] = true;
+ // 다음 행에 배치한다
+ backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2);
+ // 되돌리기: 해당 칸을 빈칸으로 되돌린다
+ state[row][col] = '#';
+ cols[col] = diags1[diag1] = diags2[diag2] = false;
+ }
+ }
+}
+
+/* n-퀸 문제 풀기 */
+char ***nQueens(int n, int *returnSize) {
+ char state[MAX_SIZE][MAX_SIZE];
+ // n*n 크기의 체스판을 초기화한다. 'Q'는 퀸, '#'은 빈칸을 나타낸다
+ for (int i = 0; i < n; ++i) {
+ for (int j = 0; j < n; ++j) {
+ state[i][j] = '#';
+ }
+ state[i][n] = '\0';
+ }
+ bool cols[MAX_SIZE] = {false}; // 열에 퀸이 있는지 기록한다
+ bool diags1[2 * MAX_SIZE - 1] = {false}; // 주대각선에 퀸이 있는지 기록한다
+ bool diags2[2 * MAX_SIZE - 1] = {false}; // 부대각선에 퀸이 있는지 기록한다
+
+ char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE);
+ *returnSize = 0;
+ backtrack(0, n, state, res, returnSize, cols, diags1, diags2);
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 4;
+ int returnSize;
+ char ***res = nQueens(n, &returnSize);
+
+ printf("입력한 체스판의 가로와 세로 길이: %d\n", n);
+ printf("퀸을 배치하는 방법은 총 %d가지이다\n", returnSize);
+ for (int i = 0; i < returnSize; ++i) {
+ for (int j = 0; j < n; ++j) {
+ printf("[");
+ for (int k = 0; res[i][j][k] != '\0'; ++k) {
+ printf("%c", res[i][j][k]);
+ if (res[i][j][k + 1] != '\0') {
+ printf(", ");
+ }
+ }
+ printf("]\n");
+ }
+ printf("---------------------\n");
+ }
+
+ // 메모리 해제
+ for (int i = 0; i < returnSize; ++i) {
+ for (int j = 0; j < n; ++j) {
+ free(res[i][j]);
+ }
+ free(res[i]);
+ }
+ free(res);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_backtracking/permutations_i.c b/ko/codes/c/chapter_backtracking/permutations_i.c
new file mode 100644
index 000000000..b65228197
--- /dev/null
+++ b/ko/codes/c/chapter_backtracking/permutations_i.c
@@ -0,0 +1,79 @@
+/**
+ * File: permutations_i.c
+ * Created Time: 2023-06-04
+ * Author: Gonglja (glj0@outlook.com), krahets (krahets@163.com)
+ */
+
+#include "../utils/common.h"
+
+// 순열이 최대 1000개라고 가정
+#define MAX_SIZE 1000
+
+/* 백트래킹 알고리즘: 모든 순열 I */
+void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) {
+ // 상태의 길이가 원소 개수와 같으면 해를 기록한다
+ if (stateSize == choicesSize) {
+ res[*resSize] = (int *)malloc(choicesSize * sizeof(int));
+ for (int i = 0; i < choicesSize; i++) {
+ res[*resSize][i] = state[i];
+ }
+ (*resSize)++;
+ return;
+ }
+ // 모든 선택지를 순회한다
+ for (int i = 0; i < choicesSize; i++) {
+ int choice = choices[i];
+ // 가지치기: 원소를 중복하여 선택할 수 없다
+ if (!selected[i]) {
+ // 시도: 선택하고 상태를 갱신한다
+ selected[i] = true;
+ state[stateSize] = choice;
+ // 다음 선택을 진행한다
+ backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize);
+ // 되돌리기: 선택을 취소하고 이전 상태로 되돌린다
+ selected[i] = false;
+ }
+ }
+}
+
+/* 모든 순열 I */
+int **permutationsI(int *nums, int numsSize, int *returnSize) {
+ int *state = (int *)malloc(numsSize * sizeof(int));
+ bool *selected = (bool *)malloc(numsSize * sizeof(bool));
+ for (int i = 0; i < numsSize; i++) {
+ selected[i] = false;
+ }
+ int **res = (int **)malloc(MAX_SIZE * sizeof(int *));
+ *returnSize = 0;
+
+ backtrack(state, 0, nums, numsSize, selected, res, returnSize);
+
+ free(state);
+ free(selected);
+
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ int nums[] = {1, 2, 3};
+ int numsSize = sizeof(nums) / sizeof(nums[0]);
+ int returnSize;
+
+ int **res = permutationsI(nums, numsSize, &returnSize);
+
+ printf("입력 배열 nums = ");
+ printArray(nums, numsSize);
+ printf("\n모든 순열 res = \n");
+ for (int i = 0; i < returnSize; i++) {
+ printArray(res[i], numsSize);
+ }
+
+ // 메모리 해제
+ for (int i = 0; i < returnSize; i++) {
+ free(res[i]);
+ }
+ free(res);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_backtracking/permutations_ii.c b/ko/codes/c/chapter_backtracking/permutations_ii.c
new file mode 100644
index 000000000..cf6526ed2
--- /dev/null
+++ b/ko/codes/c/chapter_backtracking/permutations_ii.c
@@ -0,0 +1,81 @@
+/**
+ * File: permutations_ii.c
+ * Created Time: 2023-10-17
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.h"
+
+// 순열이 최대 1000개이고 원소의 최댓값이 1000이라고 가정
+#define MAX_SIZE 1000
+
+/* 백트래킹 알고리즘: 모든 순열 II */
+void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) {
+ // 상태의 길이가 원소 개수와 같으면 해를 기록한다
+ if (stateSize == choicesSize) {
+ res[*resSize] = (int *)malloc(choicesSize * sizeof(int));
+ for (int i = 0; i < choicesSize; i++) {
+ res[*resSize][i] = state[i];
+ }
+ (*resSize)++;
+ return;
+ }
+ // 모든 선택지를 순회한다
+ bool duplicated[MAX_SIZE] = {false};
+ for (int i = 0; i < choicesSize; i++) {
+ int choice = choices[i];
+ // 가지치기: 원소를 중복하여 선택할 수 없으며, 값이 같은 원소도 중복하여 선택할 수 없다
+ if (!selected[i] && !duplicated[choice]) {
+ // 시도: 선택하고 상태를 갱신한다
+ duplicated[choice] = true; // 선택한 원소의 값을 기록한다
+ selected[i] = true;
+ state[stateSize] = choice;
+ // 다음 선택을 진행한다
+ backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize);
+ // 되돌리기: 선택을 취소하고 이전 상태로 되돌린다
+ selected[i] = false;
+ }
+ }
+}
+
+/* 모든 순열 II */
+int **permutationsII(int *nums, int numsSize, int *returnSize) {
+ int *state = (int *)malloc(numsSize * sizeof(int));
+ bool *selected = (bool *)malloc(numsSize * sizeof(bool));
+ for (int i = 0; i < numsSize; i++) {
+ selected[i] = false;
+ }
+ int **res = (int **)malloc(MAX_SIZE * sizeof(int *));
+ *returnSize = 0;
+
+ backtrack(state, 0, nums, numsSize, selected, res, returnSize);
+
+ free(state);
+ free(selected);
+
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ int nums[] = {1, 1, 2};
+ int numsSize = sizeof(nums) / sizeof(nums[0]);
+ int returnSize;
+
+ int **res = permutationsII(nums, numsSize, &returnSize);
+
+ printf("입력 배열 nums = ");
+ printArray(nums, numsSize);
+ printf("\n모든 순열 res = \n");
+ for (int i = 0; i < returnSize; i++) {
+ printArray(res[i], numsSize);
+ }
+
+ // 메모리 해제
+ for (int i = 0; i < returnSize; i++) {
+ free(res[i]);
+ }
+ free(res);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_backtracking/preorder_traversal_i_compact.c b/ko/codes/c/chapter_backtracking/preorder_traversal_i_compact.c
new file mode 100644
index 000000000..0499cc737
--- /dev/null
+++ b/ko/codes/c/chapter_backtracking/preorder_traversal_i_compact.c
@@ -0,0 +1,49 @@
+/**
+ * File: preorder_traversal_i_compact.c
+ * Created Time: 2023-05-10
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+// 결과의 길이가 100을 넘지 않는다고 가정
+#define MAX_SIZE 100
+
+TreeNode *res[MAX_SIZE];
+int resSize = 0;
+
+/* 전위 순회: 예제 1 */
+void preOrder(TreeNode *root) {
+ if (root == NULL) {
+ return;
+ }
+ if (root->val == 7) {
+ // 해를 기록한다
+ res[resSize++] = root;
+ }
+ preOrder(root->left);
+ preOrder(root->right);
+}
+
+/* 실행 코드 */
+int main() {
+ int arr[] = {1, 7, 3, 4, 5, 6, 7};
+ TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0]));
+ printf("\n이진 트리 초기화\n");
+ printTree(root);
+
+ // 전위 순회
+ preOrder(root);
+
+ printf("\n값이 7인 모든 노드 출력\n");
+ int *vals = malloc(resSize * sizeof(int));
+ for (int i = 0; i < resSize; i++) {
+ vals[i] = res[i]->val;
+ }
+ printArray(vals, resSize);
+
+ // 메모리 해제
+ freeMemoryTree(root);
+ free(vals);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c b/ko/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c
new file mode 100644
index 000000000..61c69d2e7
--- /dev/null
+++ b/ko/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c
@@ -0,0 +1,61 @@
+/**
+ * File: preorder_traversal_ii_compact.c
+ * Created Time: 2023-05-28
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+// 경로와 결과의 길이가 100을 넘지 않는다고 가정
+#define MAX_SIZE 100
+#define MAX_RES_SIZE 100
+
+TreeNode *path[MAX_SIZE];
+TreeNode *res[MAX_RES_SIZE][MAX_SIZE];
+int pathSize = 0, resSize = 0;
+
+/* 전위 순회: 예제 2 */
+void preOrder(TreeNode *root) {
+ if (root == NULL) {
+ return;
+ }
+ // 시도
+ path[pathSize++] = root;
+ if (root->val == 7) {
+ // 해를 기록한다
+ for (int i = 0; i < pathSize; ++i) {
+ res[resSize][i] = path[i];
+ }
+ resSize++;
+ }
+ preOrder(root->left);
+ preOrder(root->right);
+ // 되돌리기
+ pathSize--;
+}
+
+/* 실행 코드 */
+int main() {
+ int arr[] = {1, 7, 3, 4, 5, 6, 7};
+ TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0]));
+ printf("\n이진 트리 초기화\n");
+ printTree(root);
+
+ // 전위 순회
+ preOrder(root);
+
+ printf("\n루트 노드에서 노드 7까지의 모든 경로 출력\n");
+ for (int i = 0; i < resSize; ++i) {
+ int *vals = malloc(MAX_SIZE * sizeof(int));
+ int size = 0;
+ for (int j = 0; res[i][j] != NULL; ++j) {
+ vals[size++] = res[i][j]->val;
+ }
+ printArray(vals, size);
+ free(vals);
+ }
+
+ // 메모리 해제
+ freeMemoryTree(root);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c b/ko/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c
new file mode 100644
index 000000000..3829e5e10
--- /dev/null
+++ b/ko/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c
@@ -0,0 +1,62 @@
+/**
+ * File: preorder_traversal_iii_compact.c
+ * Created Time: 2023-06-04
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+// 경로와 결과의 길이가 100을 넘지 않는다고 가정
+#define MAX_SIZE 100
+#define MAX_RES_SIZE 100
+
+TreeNode *path[MAX_SIZE];
+TreeNode *res[MAX_RES_SIZE][MAX_SIZE];
+int pathSize = 0, resSize = 0;
+
+/* 전위 순회: 예제 3 */
+void preOrder(TreeNode *root) {
+ // 가지치기
+ if (root == NULL || root->val == 3) {
+ return;
+ }
+ // 시도
+ path[pathSize++] = root;
+ if (root->val == 7) {
+ // 해를 기록한다
+ for (int i = 0; i < pathSize; i++) {
+ res[resSize][i] = path[i];
+ }
+ resSize++;
+ }
+ preOrder(root->left);
+ preOrder(root->right);
+ // 되돌리기
+ pathSize--;
+}
+
+/* 실행 코드 */
+int main() {
+ int arr[] = {1, 7, 3, 4, 5, 6, 7};
+ TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0]));
+ printf("\n이진 트리 초기화\n");
+ printTree(root);
+
+ // 전위 순회
+ preOrder(root);
+
+ printf("\n값이 3인 노드를 포함하지 않는, 루트 노드에서 노드 7까지의 모든 경로 출력\n");
+ for (int i = 0; i < resSize; ++i) {
+ int *vals = malloc(MAX_SIZE * sizeof(int));
+ int size = 0;
+ for (int j = 0; res[i][j] != NULL; ++j) {
+ vals[size++] = res[i][j]->val;
+ }
+ printArray(vals, size);
+ free(vals);
+ }
+
+ // 메모리 해제
+ freeMemoryTree(root);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_backtracking/preorder_traversal_iii_template.c b/ko/codes/c/chapter_backtracking/preorder_traversal_iii_template.c
new file mode 100644
index 000000000..ff38a845c
--- /dev/null
+++ b/ko/codes/c/chapter_backtracking/preorder_traversal_iii_template.c
@@ -0,0 +1,93 @@
+/**
+ * File: preorder_traversal_iii_template.c
+ * Created Time: 2023-06-04
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+// 경로와 결과의 길이가 100을 넘지 않는다고 가정
+#define MAX_SIZE 100
+#define MAX_RES_SIZE 100
+
+TreeNode *path[MAX_SIZE];
+TreeNode *res[MAX_RES_SIZE][MAX_SIZE];
+int pathSize = 0, resSize = 0;
+
+/* 현재 상태가 해인지 판단 */
+bool isSolution(void) {
+ return pathSize > 0 && path[pathSize - 1]->val == 7;
+}
+
+/* 해를 기록한다 */
+void recordSolution(void) {
+ for (int i = 0; i < pathSize; i++) {
+ res[resSize][i] = path[i];
+ }
+ resSize++;
+}
+
+/* 현재 상태에서 해당 선택이 유효한지 판단 */
+bool isValid(TreeNode *choice) {
+ return choice != NULL && choice->val != 3;
+}
+
+/* 상태 갱신 */
+void makeChoice(TreeNode *choice) {
+ path[pathSize++] = choice;
+}
+
+/* 상태 복원 */
+void undoChoice(void) {
+ pathSize--;
+}
+
+/* 백트래킹 알고리즘: 예제 3 */
+void backtrack(TreeNode *choices[2]) {
+ // 해인지 확인한다
+ if (isSolution()) {
+ // 해를 기록한다
+ recordSolution();
+ }
+ // 모든 선택지를 순회한다
+ for (int i = 0; i < 2; i++) {
+ TreeNode *choice = choices[i];
+ // 가지치기: 선택이 유효한지 확인한다
+ if (isValid(choice)) {
+ // 시도: 선택하고 상태를 갱신한다
+ makeChoice(choice);
+ // 다음 선택을 진행한다
+ TreeNode *nextChoices[2] = {choice->left, choice->right};
+ backtrack(nextChoices);
+ // 되돌리기: 선택을 취소하고 이전 상태로 되돌린다
+ undoChoice();
+ }
+ }
+}
+
+/* 실행 코드 */
+int main() {
+ int arr[] = {1, 7, 3, 4, 5, 6, 7};
+ TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0]));
+ printf("\n이진 트리 초기화\n");
+ printTree(root);
+
+ // 백트래킹 알고리즘
+ TreeNode *choices[2] = {root, NULL};
+ backtrack(choices);
+
+ printf("\n값이 3인 노드를 포함하지 않는, 루트 노드에서 노드 7까지의 모든 경로 출력\n");
+ for (int i = 0; i < resSize; ++i) {
+ int *vals = malloc(MAX_SIZE * sizeof(int));
+ int size = 0;
+ for (int j = 0; res[i][j] != NULL; ++j) {
+ vals[size++] = res[i][j]->val;
+ }
+ printArray(vals, size);
+ free(vals);
+ }
+
+ // 메모리 해제
+ freeMemoryTree(root);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_backtracking/subset_sum_i.c b/ko/codes/c/chapter_backtracking/subset_sum_i.c
new file mode 100644
index 000000000..56bfdebc1
--- /dev/null
+++ b/ko/codes/c/chapter_backtracking/subset_sum_i.c
@@ -0,0 +1,78 @@
+/**
+ * File: subset_sum_i.c
+ * Created Time: 2023-07-29
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+#define MAX_SIZE 100
+#define MAX_RES_SIZE 100
+
+// 상태(부분 집합)
+int state[MAX_SIZE];
+int stateSize = 0;
+
+// 결과 리스트(부분 집합 리스트)
+int res[MAX_RES_SIZE][MAX_SIZE];
+int resColSizes[MAX_RES_SIZE];
+int resSize = 0;
+
+/* 백트래킹 알고리즘: 부분 집합 합 I */
+void backtrack(int target, int *choices, int choicesSize, int start) {
+ // 부분 집합의 합이 target과 같으면 해를 기록한다
+ if (target == 0) {
+ for (int i = 0; i < stateSize; ++i) {
+ res[resSize][i] = state[i];
+ }
+ resColSizes[resSize++] = stateSize;
+ return;
+ }
+ // 모든 선택지를 순회한다
+ // 가지치기 2: start부터 순회하여 중복 부분 집합 생성을 방지한다
+ for (int i = start; i < choicesSize; i++) {
+ // 가지치기 1: 부분 집합의 합이 target을 초과하면 즉시 반복문을 종료한다
+ // 배열이 정렬되어 있어 뒤의 원소가 더 크므로, 부분 집합의 합이 반드시 target을 초과하기 때문이다
+ if (target - choices[i] < 0) {
+ break;
+ }
+ // 시도: 선택하고 target, start를 갱신한다
+ state[stateSize] = choices[i];
+ stateSize++;
+ // 다음 선택을 진행한다
+ backtrack(target - choices[i], choices, choicesSize, i);
+ // 되돌리기: 선택을 취소하고 이전 상태로 되돌린다
+ stateSize--;
+ }
+}
+
+/* 비교 함수 */
+int cmp(const void *a, const void *b) {
+ return (*(int *)a - *(int *)b);
+}
+
+/* 부분 집합 합 I 풀기 */
+void subsetSumI(int *nums, int numsSize, int target) {
+ qsort(nums, numsSize, sizeof(int), cmp); // nums를 정렬한다
+ int start = 0; // 순회 시작 위치
+ backtrack(target, nums, numsSize, start);
+}
+
+/* 실행 코드 */
+int main() {
+ int nums[] = {3, 4, 5};
+ int numsSize = sizeof(nums) / sizeof(nums[0]);
+ int target = 9;
+
+ subsetSumI(nums, numsSize, target);
+
+ printf("입력 배열 nums = ");
+ printArray(nums, numsSize);
+ printf("target = %d\n", target);
+ printf("합이 %d인 모든 부분 집합 res = \n", target);
+ for (int i = 0; i < resSize; ++i) {
+ printArray(res[i], resColSizes[i]);
+ }
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_backtracking/subset_sum_i_naive.c b/ko/codes/c/chapter_backtracking/subset_sum_i_naive.c
new file mode 100644
index 000000000..36b69c899
--- /dev/null
+++ b/ko/codes/c/chapter_backtracking/subset_sum_i_naive.c
@@ -0,0 +1,69 @@
+/**
+ * File: subset_sum_i_naive.c
+ * Created Time: 2023-07-28
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+#define MAX_SIZE 100
+#define MAX_RES_SIZE 100
+
+// 상태(부분 집합)
+int state[MAX_SIZE];
+int stateSize = 0;
+
+// 결과 리스트(부분 집합 리스트)
+int res[MAX_RES_SIZE][MAX_SIZE];
+int resColSizes[MAX_RES_SIZE];
+int resSize = 0;
+
+/* 백트래킹 알고리즘: 부분 집합 합 I */
+void backtrack(int target, int total, int *choices, int choicesSize) {
+ // 부분 집합의 합이 target과 같으면 해를 기록한다
+ if (total == target) {
+ for (int i = 0; i < stateSize; i++) {
+ res[resSize][i] = state[i];
+ }
+ resColSizes[resSize++] = stateSize;
+ return;
+ }
+ // 모든 선택지를 순회한다
+ for (int i = 0; i < choicesSize; i++) {
+ // 가지치기: 부분 집합의 합이 target을 초과하면 해당 선택을 건너뛴다
+ if (total + choices[i] > target) {
+ continue;
+ }
+ // 시도: 선택하고 원소의 합 total을 갱신한다
+ state[stateSize++] = choices[i];
+ // 다음 선택을 진행한다
+ backtrack(target, total + choices[i], choices, choicesSize);
+ // 되돌리기: 선택을 취소하고 이전 상태로 되돌린다
+ stateSize--;
+ }
+}
+
+/* 부분 집합 합 I 풀기(중복 부분 집합 포함) */
+void subsetSumINaive(int *nums, int numsSize, int target) {
+ resSize = 0; // 해의 개수를 0으로 초기화
+ backtrack(target, 0, nums, numsSize);
+}
+
+/* 실행 코드 */
+int main() {
+ int nums[] = {3, 4, 5};
+ int numsSize = sizeof(nums) / sizeof(nums[0]);
+ int target = 9;
+
+ subsetSumINaive(nums, numsSize, target);
+
+ printf("입력 배열 nums = ");
+ printArray(nums, numsSize);
+ printf("target = %d\n", target);
+ printf("합이 %d인 모든 부분 집합 res = \n", target);
+ for (int i = 0; i < resSize; i++) {
+ printArray(res[i], resColSizes[i]);
+ }
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_backtracking/subset_sum_ii.c b/ko/codes/c/chapter_backtracking/subset_sum_ii.c
new file mode 100644
index 000000000..f1c3886fa
--- /dev/null
+++ b/ko/codes/c/chapter_backtracking/subset_sum_ii.c
@@ -0,0 +1,83 @@
+/**
+ * File: subset_sum_ii.c
+ * Created Time: 2023-07-29
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+#define MAX_SIZE 100
+#define MAX_RES_SIZE 100
+
+// 상태(부분 집합)
+int state[MAX_SIZE];
+int stateSize = 0;
+
+// 결과 리스트(부분 집합 리스트)
+int res[MAX_RES_SIZE][MAX_SIZE];
+int resColSizes[MAX_RES_SIZE];
+int resSize = 0;
+
+/* 백트래킹 알고리즘: 부분 집합 합 II */
+void backtrack(int target, int *choices, int choicesSize, int start) {
+ // 부분 집합의 합이 target과 같으면 해를 기록한다
+ if (target == 0) {
+ for (int i = 0; i < stateSize; i++) {
+ res[resSize][i] = state[i];
+ }
+ resColSizes[resSize++] = stateSize;
+ return;
+ }
+ // 모든 선택지를 순회한다
+ // 가지치기 2: start부터 순회하여 중복 부분 집합 생성을 방지한다
+ // 가지치기 3: start부터 순회하여 같은 원소의 중복 선택을 방지한다
+ for (int i = start; i < choicesSize; i++) {
+ // 가지치기 1: 부분 집합의 합이 target을 넘으면 바로 건너뛴다
+ if (target - choices[i] < 0) {
+ continue;
+ }
+ // 가지치기 4: 해당 원소가 왼쪽 원소와 같으면 탐색 분기가 중복되므로 바로 건너뛴다
+ if (i > start && choices[i] == choices[i - 1]) {
+ continue;
+ }
+ // 시도: 선택하고 target, start를 갱신한다
+ state[stateSize] = choices[i];
+ stateSize++;
+ // 다음 선택을 진행한다
+ backtrack(target - choices[i], choices, choicesSize, i + 1);
+ // 되돌리기: 선택을 취소하고 이전 상태로 되돌린다
+ stateSize--;
+ }
+}
+
+/* 비교 함수 */
+int cmp(const void *a, const void *b) {
+ return (*(int *)a - *(int *)b);
+}
+
+/* 부분 집합 합 II 풀기 */
+void subsetSumII(int *nums, int numsSize, int target) {
+ // nums를 정렬한다
+ qsort(nums, numsSize, sizeof(int), cmp);
+ // 백트래킹 시작
+ backtrack(target, nums, numsSize, 0);
+}
+
+/* 실행 코드 */
+int main() {
+ int nums[] = {4, 4, 5};
+ int numsSize = sizeof(nums) / sizeof(nums[0]);
+ int target = 9;
+
+ subsetSumII(nums, numsSize, target);
+
+ printf("입력 배열 nums = ");
+ printArray(nums, numsSize);
+ printf("target = %d\n", target);
+ printf("합이 %d인 모든 부분 집합 res = \n", target);
+ for (int i = 0; i < resSize; ++i) {
+ printArray(res[i], resColSizes[i]);
+ }
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_computational_complexity/CMakeLists.txt b/ko/codes/c/chapter_computational_complexity/CMakeLists.txt
new file mode 100644
index 000000000..3d2450c26
--- /dev/null
+++ b/ko/codes/c/chapter_computational_complexity/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_executable(iteration iteration.c)
+add_executable(recursion recursion.c)
+add_executable(time_complexity time_complexity.c)
+add_executable(worst_best_time_complexity worst_best_time_complexity.c)
+add_executable(space_complexity space_complexity.c)
+add_executable(complexity_exercises complexity_exercises.c)
diff --git a/ko/codes/c/chapter_computational_complexity/complexity_exercises.c b/ko/codes/c/chapter_computational_complexity/complexity_exercises.c
new file mode 100644
index 000000000..01c36ba31
--- /dev/null
+++ b/ko/codes/c/chapter_computational_complexity/complexity_exercises.c
@@ -0,0 +1,61 @@
+/**
+ * File: complexity_exercises.c
+ * Created Time: 2026-08-18
+ * Author: Hello Algo Team
+ */
+
+#include
+
+/* 반복으로 합 구하기 */
+int sumIter(int n) {
+ int res = 0;
+ for (int i = 1; i <= n; i++) {
+ res += i;
+ }
+ return res;
+}
+
+/* 재귀로 합 구하기 */
+int sumRecur(int n) {
+ if (n == 1) {
+ return 1;
+ }
+ return n + sumRecur(n - 1);
+}
+
+/* 선형 차수 반복문 */
+int linearLoop(int n) {
+ int res = 0;
+ for (int i = 0; i < n; i++) {
+ res += i;
+ }
+ return res;
+}
+
+/* 제곱 차수 반복문 */
+int quadraticLoop(int n) {
+ int res = 0;
+ for (int i = 0; i < n; i++) {
+ for (int j = i; j < n; j++) {
+ res += j;
+ }
+ }
+ return res;
+}
+
+/* 로그 차수 반복문 */
+int logarithmicLoop(int n) {
+ while (n > 1) {
+ n /= 2;
+ }
+ return n;
+}
+
+int main(void) {
+ assert(sumIter(1) == 1 && sumRecur(1) == 1);
+ assert(sumIter(4) == 10 && sumRecur(4) == 10);
+ assert(linearLoop(4) == 6);
+ assert(quadraticLoop(4) == 20);
+ assert(logarithmicLoop(4) == 1 && logarithmicLoop(5) == 1);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_computational_complexity/iteration.c b/ko/codes/c/chapter_computational_complexity/iteration.c
new file mode 100644
index 000000000..398dead8d
--- /dev/null
+++ b/ko/codes/c/chapter_computational_complexity/iteration.c
@@ -0,0 +1,81 @@
+/**
+ * File: iteration.c
+ * Created Time: 2023-09-09
+ * Author: Gonglja (glj0@outlook.com), MwumLi (mwumli@hotmail.com)
+ */
+
+#include "../utils/common.h"
+
+/* for 반복문 */
+int forLoop(int n) {
+ int res = 0;
+ // 반복문으로 1, 2, ..., n-1, n의 합을 구한다
+ for (int i = 1; i <= n; i++) {
+ res += i;
+ }
+ return res;
+}
+
+/* while 반복문 */
+int whileLoop(int n) {
+ int res = 0;
+ int i = 1; // 조건 변수를 초기화한다
+ // 반복문으로 1, 2, ..., n-1, n의 합을 구한다
+ while (i <= n) {
+ res += i;
+ i++; // 조건 변수를 갱신한다
+ }
+ return res;
+}
+
+/* while 반복문(두 번 갱신) */
+int whileLoopII(int n) {
+ int res = 0;
+ int i = 1; // 조건 변수를 초기화한다
+ // 반복문으로 1, 4, 10, ...의 합을 구한다
+ while (i <= n) {
+ res += i;
+ // 조건 변수를 갱신한다
+ i++;
+ i *= 2;
+ }
+ return res;
+}
+
+/* 이중 for 반복문 */
+char *nestedForLoop(int n) {
+ // n * n은 해당 점의 개수이며, "(i, j), "의 최대 문자열 길이는 6+10*2이다. 마지막 널 문자 \0을 위한 공간도 추가한다
+ int size = n * n * 26 + 1;
+ char *res = malloc(size * sizeof(char));
+ // i = 1, 2, ..., n-1, n을 반복한다
+ for (int i = 1; i <= n; i++) {
+ // j = 1, 2, ..., n-1, n을 반복한다
+ for (int j = 1; j <= n; j++) {
+ char tmp[26];
+ snprintf(tmp, sizeof(tmp), "(%d, %d), ", i, j);
+ strncat(res, tmp, size - strlen(res) - 1);
+ }
+ }
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 5;
+ int res;
+
+ res = forLoop(n);
+ printf("\nfor 반복문으로 구한 합 res = %d\n", res);
+
+ res = whileLoop(n);
+ printf("\nwhile 반복문으로 구한 합 res = %d\n", res);
+
+ res = whileLoopII(n);
+ printf("\nwhile 반복문(두 번 갱신)으로 구한 합 res = %d\n", res);
+
+ char *resStr = nestedForLoop(n);
+ printf("\n이중 for 반복문의 순회 결과 %s\r\n", resStr);
+ free(resStr);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_computational_complexity/recursion.c b/ko/codes/c/chapter_computational_complexity/recursion.c
new file mode 100644
index 000000000..8f2d27d90
--- /dev/null
+++ b/ko/codes/c/chapter_computational_complexity/recursion.c
@@ -0,0 +1,77 @@
+/**
+ * File: recursion.c
+ * Created Time: 2023-09-09
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 재귀 */
+int recur(int n) {
+ // 종료 조건
+ if (n == 1)
+ return 1;
+ // 내려가기: 재귀 호출
+ int res = recur(n - 1);
+ // 돌아오기: 결과 반환
+ return n + res;
+}
+
+/* 반복으로 재귀 구현 */
+int forLoopRecur(int n) {
+ int stack[1000]; // 큰 배열을 사용하여 스택을 구현한다
+ int top = -1; // 스택의 맨 위 인덱스
+ int res = 0;
+ // 내려가기: 재귀 호출
+ for (int i = n; i > 0; i--) {
+ // 스택에 삽입하여 내려가는 과정을 모방한다
+ stack[1 + top++] = i;
+ }
+ // 돌아오기: 결과 반환
+ while (top >= 0) {
+ // 스택에서 꺼내어 돌아오는 과정을 모방한다
+ res += stack[top--];
+ }
+ // res = 1+2+3+...+n
+ return res;
+}
+
+/* 꼬리 재귀 */
+int tailRecur(int n, int res) {
+ // 종료 조건
+ if (n == 0)
+ return res;
+ // 꼬리 재귀 호출
+ return tailRecur(n - 1, res + n);
+}
+
+/* 피보나치 수열: 재귀 */
+int fib(int n) {
+ // 종료 조건 f(1) = 0, f(2) = 1
+ if (n == 1 || n == 2)
+ return n - 1;
+ // 재귀 호출 f(n) = f(n-1) + f(n-2)
+ int res = fib(n - 1) + fib(n - 2);
+ // 결과 f(n)을 반환한다
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 5;
+ int res;
+
+ res = recur(n);
+ printf("\n재귀 함수로 구한 합 res = %d\n", res);
+
+ res = forLoopRecur(n);
+ printf("\n반복으로 재귀를 구현하여 구한 합 res = %d\n", res);
+
+ res = tailRecur(n, 0);
+ printf("\n꼬리 재귀 함수로 구한 합 res = %d\n", res);
+
+ res = fib(n);
+ printf("\n피보나치 수열의 %d번째 항은 %d이다\n", n, res);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_computational_complexity/space_complexity.c b/ko/codes/c/chapter_computational_complexity/space_complexity.c
new file mode 100644
index 000000000..4b7887784
--- /dev/null
+++ b/ko/codes/c/chapter_computational_complexity/space_complexity.c
@@ -0,0 +1,141 @@
+/**
+ * File: space_complexity.c
+ * Created Time: 2023-04-15
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 함수 */
+int func() {
+ // 몇 가지 연산을 수행한다
+ return 0;
+}
+
+/* 상수 차수 */
+void constant(int n) {
+ // 상수, 변수, 객체는 O(1) 공간을 차지한다
+ const int a = 0;
+ int b = 0;
+ int nums[1000];
+ ListNode *node = newListNode(0);
+ free(node);
+ // 반복문 안의 변수는 O(1) 공간을 차지한다
+ for (int i = 0; i < n; i++) {
+ int c = 0;
+ }
+ // 반복문 안의 함수는 O(1) 공간을 차지한다
+ for (int i = 0; i < n; i++) {
+ func();
+ }
+}
+
+/* 해시 테이블 */
+typedef struct {
+ int key;
+ int val;
+ UT_hash_handle hh; // uthash.h를 기반으로 구현한다
+} HashTable;
+
+/* 선형 차수 */
+void linear(int n) {
+ // 길이가 n인 배열은 O(n) 공간을 차지한다
+ int *nums = malloc(sizeof(int) * n);
+ free(nums);
+
+ // 길이가 n인 리스트는 O(n) 공간을 차지한다
+ ListNode **nodes = malloc(sizeof(ListNode *) * n);
+ for (int i = 0; i < n; i++) {
+ nodes[i] = newListNode(i);
+ }
+ // 메모리 해제
+ for (int i = 0; i < n; i++) {
+ free(nodes[i]);
+ }
+ free(nodes);
+
+ // 길이가 n인 해시 테이블은 O(n) 공간을 차지한다
+ HashTable *h = NULL;
+ for (int i = 0; i < n; i++) {
+ HashTable *tmp = malloc(sizeof(HashTable));
+ tmp->key = i;
+ tmp->val = i;
+ HASH_ADD_INT(h, key, tmp);
+ }
+
+ // 메모리 해제
+ HashTable *curr, *tmp;
+ HASH_ITER(hh, h, curr, tmp) {
+ HASH_DEL(h, curr);
+ free(curr);
+ }
+}
+
+/* 선형 차수(재귀 구현) */
+void linearRecur(int n) {
+ printf("재귀 n = %d\r\n", n);
+ if (n == 1)
+ return;
+ linearRecur(n - 1);
+}
+
+/* 제곱 차수 */
+void quadratic(int n) {
+ // 이차원 리스트는 O(n^2) 공간을 차지한다
+ int **numMatrix = malloc(sizeof(int *) * n);
+ for (int i = 0; i < n; i++) {
+ int *tmp = malloc(sizeof(int) * n);
+ for (int j = 0; j < n; j++) {
+ tmp[j] = 0;
+ }
+ numMatrix[i] = tmp;
+ }
+
+ // 메모리 해제
+ for (int i = 0; i < n; i++) {
+ free(numMatrix[i]);
+ }
+ free(numMatrix);
+}
+
+/* 제곱 차수(재귀 구현) */
+int quadraticRecur(int n) {
+ if (n <= 0)
+ return 0;
+ int *nums = malloc(sizeof(int) * n);
+ printf("재귀 n = %d에서 nums의 길이 = %d\r\n", n, n);
+ int res = quadraticRecur(n - 1);
+ free(nums);
+ return res;
+}
+
+/* 지수 차수(포화 이진 트리 구성) */
+TreeNode *buildTree(int n) {
+ if (n == 0)
+ return NULL;
+ TreeNode *root = newTreeNode(0);
+ root->left = buildTree(n - 1);
+ root->right = buildTree(n - 1);
+ return root;
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 5;
+ // 상수 차수
+ constant(n);
+ // 선형 차수
+ linear(n);
+ linearRecur(n);
+ // 제곱 차수
+ quadratic(n);
+ quadraticRecur(n);
+ // 지수 차수
+ TreeNode *root = buildTree(n);
+ printTree(root);
+
+ // 메모리 해제
+ freeMemoryTree(root);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_computational_complexity/time_complexity.c b/ko/codes/c/chapter_computational_complexity/time_complexity.c
new file mode 100644
index 000000000..f79debbde
--- /dev/null
+++ b/ko/codes/c/chapter_computational_complexity/time_complexity.c
@@ -0,0 +1,178 @@
+/**
+ * File: time_complexity.c
+ * Created Time: 2023-01-03
+ * Author: codingonion (coderonion@gmail.com)
+ */
+
+#include "../utils/common.h"
+
+/* 상수 차수 */
+int constant(int n) {
+ int count = 0;
+ int size = 100000;
+ for (int i = 0; i < size; i++) {
+ count++;
+ }
+ return count;
+}
+
+/* 선형 차수 */
+int linear(int n) {
+ int count = 0;
+ for (int i = 0; i < n; i++) {
+ count++;
+ }
+ return count;
+}
+
+/* 선형 차수(배열 순회) */
+int arrayTraversal(int *nums, int n) {
+ int count = 0;
+ // 반복 횟수는 배열 길이에 비례한다
+ for (int i = 0; i < n; i++) {
+ count++;
+ }
+ return count;
+}
+
+/* 제곱 차수 */
+int quadratic(int n) {
+ int count = 0;
+ // 반복 횟수는 데이터 크기 n의 제곱에 비례한다
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < n; j++) {
+ count++;
+ }
+ }
+ return count;
+}
+
+/* 제곱 차수(버블 정렬) */
+int bubbleSort(int *nums, int n) {
+ int count = 0; // 카운터
+ // 바깥쪽 반복문: 정렬되지 않은 구간은 [0, i]이다
+ for (int i = n - 1; i > 0; i--) {
+ // 안쪽 반복문: 정렬되지 않은 구간 [0, i]의 최댓값을 교환하여 이 구간의 맨 오른쪽으로 옮긴다
+ for (int j = 0; j < i; j++) {
+ if (nums[j] > nums[j + 1]) {
+ // nums[j]와 nums[j + 1]을 교환한다
+ int tmp = nums[j];
+ nums[j] = nums[j + 1];
+ nums[j + 1] = tmp;
+ count += 3; // 원소 교환에는 단위 연산 3회가 포함된다
+ }
+ }
+ }
+ return count;
+}
+
+/* 지수 차수(반복문 구현) */
+int exponential(int n) {
+ int count = 0;
+ int bas = 1;
+ // 세포가 매번 둘로 나뉘어 수열 1, 2, 4, 8, ..., 2^(n-1)을 이룬다
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < bas; j++) {
+ count++;
+ }
+ bas *= 2;
+ }
+ // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
+ return count;
+}
+
+/* 지수 차수(재귀 구현) */
+int expRecur(int n) {
+ if (n == 1)
+ return 1;
+ return expRecur(n - 1) + expRecur(n - 1) + 1;
+}
+
+/* 로그 차수(반복문 구현) */
+int logarithmic(int n) {
+ int count = 0;
+ while (n > 1) {
+ n = n / 2;
+ count++;
+ }
+ return count;
+}
+
+/* 로그 차수(재귀 구현) */
+int logRecur(int n) {
+ if (n <= 1)
+ return 0;
+ return logRecur(n / 2) + 1;
+}
+
+/* 선형 로그 차수 */
+int linearLogRecur(int n) {
+ if (n <= 1)
+ return 1;
+ int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);
+ for (int i = 0; i < n; i++) {
+ count++;
+ }
+ return count;
+}
+
+/* 팩토리얼 차수(재귀 구현) */
+int factorialRecur(int n) {
+ if (n == 0)
+ return 1;
+ int count = 0;
+ for (int i = 0; i < n; i++) {
+ count += factorialRecur(n - 1);
+ }
+ return count;
+}
+
+/* 실행 코드 */
+int main(int argc, char *argv[]) {
+ // n을 바꾸어 실행하면서 각 복잡도에서 연산 횟수가 어떻게 변하는지 살펴볼 수 있다
+ int n = 8;
+ printf("입력 데이터 크기 n = %d\n", n);
+
+ int count = constant(n);
+ printf("상수 차수의 연산 횟수 = %d\n", count);
+
+ count = linear(n);
+ printf("선형 차수의 연산 횟수 = %d\n", count);
+ // 힙 메모리를 할당한다(원소 개수가 n이고 원소 타입이 int인 1차원 가변 길이 배열을 생성한다)
+ int *nums = (int *)malloc(n * sizeof(int));
+ count = arrayTraversal(nums, n);
+ printf("선형 차수(배열 순회)의 연산 횟수 = %d\n", count);
+
+ count = quadratic(n);
+ printf("제곱 차수의 연산 횟수 = %d\n", count);
+ for (int i = 0; i < n; i++) {
+ nums[i] = n - i; // [n,n-1,...,2,1]
+ }
+ count = bubbleSort(nums, n);
+ printf("제곱 차수(버블 정렬)의 연산 횟수 = %d\n", count);
+
+ count = exponential(n);
+ printf("지수 차수(반복문 구현)의 연산 횟수 = %d\n", count);
+ count = expRecur(n);
+ printf("지수 차수(재귀 구현)의 연산 횟수 = %d\n", count);
+
+ count = logarithmic(n);
+ printf("로그 차수(반복문 구현)의 연산 횟수 = %d\n", count);
+ count = logRecur(n);
+ printf("로그 차수(재귀 구현)의 연산 횟수 = %d\n", count);
+
+ count = linearLogRecur(n);
+ printf("선형 로그 차수(재귀 구현)의 연산 횟수 = %d\n", count);
+
+ count = factorialRecur(n);
+ printf("팩토리얼 차수(재귀 구현)의 연산 횟수 = %d\n", count);
+
+ // 힙 메모리를 해제한다
+ if (nums != NULL) {
+ free(nums);
+ nums = NULL;
+ }
+ getchar();
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_computational_complexity/worst_best_time_complexity.c b/ko/codes/c/chapter_computational_complexity/worst_best_time_complexity.c
new file mode 100644
index 000000000..35d45ad79
--- /dev/null
+++ b/ko/codes/c/chapter_computational_complexity/worst_best_time_complexity.c
@@ -0,0 +1,57 @@
+/**
+ * File: worst_best_time_complexity.c
+ * Created Time: 2023-01-03
+ * Author: codingonion (coderonion@gmail.com)
+ */
+
+#include "../utils/common.h"
+
+/* 원소가 { 1, 2, ..., n }이며 순서가 섞인 배열 생성 */
+int *randomNumbers(int n) {
+ // 힙 메모리를 할당한다(원소 개수가 n이고 원소 타입이 int인 1차원 가변 길이 배열을 생성한다)
+ int *nums = (int *)malloc(n * sizeof(int));
+ // 배열 nums = { 1, 2, 3, ..., n }을 생성한다
+ for (int i = 0; i < n; i++) {
+ nums[i] = i + 1;
+ }
+ // 배열 원소를 무작위로 섞는다
+ for (int i = n - 1; i > 0; i--) {
+ int j = rand() % (i + 1);
+ int temp = nums[i];
+ nums[i] = nums[j];
+ nums[j] = temp;
+ }
+ return nums;
+}
+
+/* 배열 nums에서 숫자 1이 있는 인덱스 탐색 */
+int findOne(int *nums, int n) {
+ for (int i = 0; i < n; i++) {
+ // 원소 1이 배열 맨 앞에 있으면 최선 시간 복잡도 O(1)에 도달한다
+ // 원소 1이 배열 맨 뒤에 있으면 최악 시간 복잡도 O(n)에 도달한다
+ if (nums[i] == 1)
+ return i;
+ }
+ return -1;
+}
+
+/* 실행 코드 */
+int main(int argc, char *argv[]) {
+ // 난수 시드를 초기화한다
+ srand((unsigned int)time(NULL));
+ for (int i = 0; i < 10; i++) {
+ int n = 100;
+ int *nums = randomNumbers(n);
+ int index = findOne(nums, n);
+ printf("\n배열 [ 1, 2, ..., n ]을 섞은 결과 = ");
+ printArray(nums, n);
+ printf("숫자 1의 인덱스는 %d이다\n", index);
+ // 힙 메모리를 해제한다
+ if (nums != NULL) {
+ free(nums);
+ nums = NULL;
+ }
+ }
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_divide_and_conquer/CMakeLists.txt b/ko/codes/c/chapter_divide_and_conquer/CMakeLists.txt
new file mode 100644
index 000000000..31352a138
--- /dev/null
+++ b/ko/codes/c/chapter_divide_and_conquer/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_executable(binary_search_recur binary_search_recur.c)
+add_executable(build_tree build_tree.c)
+add_executable(hanota hanota.c)
+add_executable(fast_power fast_power.c)
diff --git a/ko/codes/c/chapter_divide_and_conquer/binary_search_recur.c b/ko/codes/c/chapter_divide_and_conquer/binary_search_recur.c
new file mode 100644
index 000000000..0865087ca
--- /dev/null
+++ b/ko/codes/c/chapter_divide_and_conquer/binary_search_recur.c
@@ -0,0 +1,47 @@
+/**
+ * File: binary_search_recur.c
+ * Created Time: 2023-10-01
+ * Author: Zuoxun (845242523@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 이진 탐색: 문제 f(i, j) */
+int dfs(int nums[], int target, int i, int j) {
+ // 구간이 비어 있으면 목표 원소가 없다는 뜻이므로 -1을 반환한다
+ if (i > j) {
+ return -1;
+ }
+ // 중간 인덱스 m을 계산한다
+ int m = (i + j) / 2;
+ if (nums[m] < target) {
+ // 부분 문제 f(m+1, j)를 재귀적으로 푼다
+ return dfs(nums, target, m + 1, j);
+ } else if (nums[m] > target) {
+ // 부분 문제 f(i, m-1)를 재귀적으로 푼다
+ return dfs(nums, target, i, m - 1);
+ } else {
+ // 목표 원소를 찾았으므로 해당 인덱스를 반환한다
+ return m;
+ }
+}
+
+/* 이진 탐색 */
+int binarySearch(int nums[], int target, int numsSize) {
+ int n = numsSize;
+ // 문제 f(0, n-1)을 푼다
+ return dfs(nums, target, 0, n - 1);
+}
+
+/* 실행 코드 */
+int main() {
+ int target = 6;
+ int nums[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35};
+ int numsSize = sizeof(nums) / sizeof(nums[0]);
+
+ // 이진 탐색(양 끝을 포함하는 닫힌 구간)
+ int index = binarySearch(nums, target, numsSize);
+ printf("목표 원소 6의 인덱스 = %d\n", index);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_divide_and_conquer/build_tree.c b/ko/codes/c/chapter_divide_and_conquer/build_tree.c
new file mode 100644
index 000000000..cfb48b4f2
--- /dev/null
+++ b/ko/codes/c/chapter_divide_and_conquer/build_tree.c
@@ -0,0 +1,61 @@
+/**
+ * File : build_tree.c
+ * Created Time: 2023-10-16
+ * Author : lucas (superrat6@gmail.com)
+ */
+
+#include "../utils/common.h"
+
+// 모든 원소가 1000보다 작다고 가정한다
+#define MAX_SIZE 1000
+
+/* 이진 트리 구성: 분할 정복 */
+TreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) {
+ // 서브트리 구간이 비어 있으면 종료한다
+ if (r - l < 0)
+ return NULL;
+ // 루트 노드를 초기화한다
+ TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode));
+ root->val = preorder[i];
+ root->left = NULL;
+ root->right = NULL;
+ // m을 조회하여 왼쪽과 오른쪽 서브트리를 나눈다
+ int m = inorderMap[preorder[i]];
+ // 부분 문제: 왼쪽 서브트리 구성
+ root->left = dfs(preorder, inorderMap, i + 1, l, m - 1, size);
+ // 부분 문제: 오른쪽 서브트리 구성
+ root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size);
+ // 루트 노드를 반환한다
+ return root;
+}
+
+/* 이진 트리 구성 */
+TreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) {
+ // inorder 원소와 인덱스의 대응 관계를 저장할 해시 테이블을 초기화한다
+ int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE);
+ for (int i = 0; i < inorderSize; i++) {
+ inorderMap[inorder[i]] = i;
+ }
+ TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize);
+ free(inorderMap);
+ return root;
+}
+
+/* 실행 코드 */
+int main() {
+ int preorder[] = {3, 9, 2, 1, 7};
+ int inorder[] = {9, 3, 1, 2, 7};
+ int preorderSize = sizeof(preorder) / sizeof(preorder[0]);
+ int inorderSize = sizeof(inorder) / sizeof(inorder[0]);
+ printf("전위 순회 = ");
+ printArray(preorder, preorderSize);
+ printf("중위 순회 = ");
+ printArray(inorder, inorderSize);
+
+ TreeNode *root = buildTree(preorder, preorderSize, inorder, inorderSize);
+ printf("구성한 이진 트리:\n");
+ printTree(root);
+
+ freeMemoryTree(root);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_divide_and_conquer/fast_power.c b/ko/codes/c/chapter_divide_and_conquer/fast_power.c
new file mode 100644
index 000000000..6298c3416
--- /dev/null
+++ b/ko/codes/c/chapter_divide_and_conquer/fast_power.c
@@ -0,0 +1,26 @@
+/**
+ * File: fast_power.c
+ * Created Time: 2026-08-18
+ * Author: Hello Algo Team
+ */
+
+#include
+
+/* 빠른 거듭제곱 */
+int fastPow(int x, int n) {
+ if (n == 0) {
+ return 1;
+ }
+ int half = fastPow(x, n / 2);
+ if (n % 2 == 0) {
+ return half * half;
+ }
+ return half * half * x;
+}
+
+int main(void) {
+ assert(fastPow(7, 0) == 1);
+ assert(fastPow(3, 5) == 243);
+ assert(fastPow(2, 6) == 64);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_divide_and_conquer/hanota.c b/ko/codes/c/chapter_divide_and_conquer/hanota.c
new file mode 100644
index 000000000..6a7137fb1
--- /dev/null
+++ b/ko/codes/c/chapter_divide_and_conquer/hanota.c
@@ -0,0 +1,74 @@
+/**
+ * File: hanota.c
+ * Created Time: 2023-10-01
+ * Author: Zuoxun (845242523@qq.com), lucas(superrat6@gmail.com)
+ */
+
+#include "../utils/common.h"
+
+// 순열이 최대 1000개라고 가정
+#define MAX_SIZE 1000
+
+/* 원판 하나 이동 */
+void move(int *src, int *srcSize, int *tar, int *tarSize) {
+ // src의 맨 위에서 원판 하나를 꺼낸다
+ int pan = src[*srcSize - 1];
+ src[*srcSize - 1] = 0;
+ (*srcSize)--;
+ // 원판을 tar의 맨 위에 놓는다
+ tar[*tarSize] = pan;
+ (*tarSize)++;
+}
+
+/* 하노이의 탑 문제 f(i) 풀기 */
+void dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) {
+ // src에 원판이 하나만 남아 있으면 바로 tar로 옮긴다
+ if (i == 1) {
+ move(src, srcSize, tar, tarSize);
+ return;
+ }
+ // 부분 문제 f(i-1): tar를 이용하여 src의 맨 위 원판 i-1개를 buf로 옮긴다
+ dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize);
+ // 부분 문제 f(1): src에 남은 원판 하나를 tar로 옮긴다
+ move(src, srcSize, tar, tarSize);
+ // 부분 문제 f(i-1): src를 이용하여 buf의 맨 위 원판 i-1개를 tar로 옮긴다
+ dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize);
+}
+
+/* 하노이의 탑 문제 풀기 */
+void solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) {
+ // B를 이용하여 A의 맨 위 원판 n개를 C로 옮긴다
+ dfs(*ASize, A, ASize, B, BSize, C, CSize);
+}
+
+/* 실행 코드 */
+int main() {
+ // 리스트의 맨 뒤가 기둥의 맨 위이다
+ int a[] = {5, 4, 3, 2, 1};
+ int b[MAX_SIZE] = {0};
+ int c[MAX_SIZE] = {0};
+
+ int ASize = sizeof(a) / sizeof(a[0]);
+ int BSize = 0;
+ int CSize = 0;
+
+ printf("\n초기 상태:");
+ printf("\nA = ");
+ printArray(a, ASize);
+ printf("B = ");
+ printArray(b, BSize);
+ printf("C = ");
+ printArray(c, CSize);
+
+ solveHanota(a, &ASize, b, &BSize, c, &CSize);
+
+ printf("\n원판 이동을 마친 후:");
+ printf("A = ");
+ printArray(a, ASize);
+ printf("B = ");
+ printArray(b, BSize);
+ printf("C = ");
+ printArray(c, CSize);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_dynamic_programming/CMakeLists.txt b/ko/codes/c/chapter_dynamic_programming/CMakeLists.txt
new file mode 100644
index 000000000..dd769ebd5
--- /dev/null
+++ b/ko/codes/c/chapter_dynamic_programming/CMakeLists.txt
@@ -0,0 +1,8 @@
+add_executable(climbing_stairs_constraint_dp climbing_stairs_constraint_dp.c)
+add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.c)
+add_executable(min_path_sum min_path_sum.c)
+add_executable(knapsack knapsack.c)
+add_executable(unbounded_knapsack unbounded_knapsack.c)
+add_executable(coin_change coin_change.c)
+add_executable(coin_change_ii coin_change_ii.c)
+add_executable(edit_distance edit_distance.c)
diff --git a/ko/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c b/ko/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c
new file mode 100644
index 000000000..7439794c3
--- /dev/null
+++ b/ko/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c
@@ -0,0 +1,47 @@
+/**
+ * File: climbing_stairs_backtrack.c
+ * Created Time: 2023-09-22
+ * Author: huawuque404 (huawuque404@163.com)
+ */
+
+#include "../utils/common.h"
+
+/* 백트래킹 */
+void backtrack(int *choices, int state, int n, int *res, int len) {
+ // n번째 계단에 도달하면 방법의 수에 1을 더한다
+ if (state == n)
+ res[0]++;
+ // 모든 선택지를 순회한다
+ for (int i = 0; i < len; i++) {
+ int choice = choices[i];
+ // 가지치기: n번째 계단을 넘어갈 수 없다
+ if (state + choice > n)
+ continue;
+ // 시도: 선택하고 상태를 갱신한다
+ backtrack(choices, state + choice, n, res, len);
+ // 되돌리기
+ }
+}
+
+/* 계단 오르기: 백트래킹 */
+int climbingStairsBacktrack(int n) {
+ int choices[2] = {1, 2}; // 한 계단 또는 두 계단 오르기를 선택할 수 있다
+ int state = 0; // 0번째 계단에서 오르기 시작한다
+ int *res = (int *)malloc(sizeof(int));
+ *res = 0; // res[0]에 방법의 수를 기록한다
+ int len = sizeof(choices) / sizeof(int);
+ backtrack(choices, state, n, res, len);
+ int result = *res;
+ free(res);
+ return result;
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 9;
+
+ int res = climbingStairsBacktrack(n);
+ printf("%d개의 계단을 오르는 방법은 총 %d가지이다\n", n, res);
+
+ return 0;
+}
\ No newline at end of file
diff --git a/ko/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c b/ko/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c
new file mode 100644
index 000000000..a986a9b82
--- /dev/null
+++ b/ko/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c
@@ -0,0 +1,46 @@
+/**
+ * File: climbing_stairs_constraint_dp.c
+ * Created Time: 2023-10-02
+ * Author: Zuoxun (845242523@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 제약 조건이 있는 계단 오르기: 동적 계획법 */
+int climbingStairsConstraintDP(int n) {
+ if (n == 1 || n == 2) {
+ return 1;
+ }
+ // 부분 문제의 해를 저장할 dp 테이블을 초기화한다
+ int **dp = malloc((n + 1) * sizeof(int *));
+ for (int i = 0; i <= n; i++) {
+ dp[i] = calloc(3, sizeof(int));
+ }
+ // 초기 상태: 가장 작은 부분 문제의 해를 미리 설정한다
+ dp[1][1] = 1;
+ dp[1][2] = 0;
+ dp[2][1] = 0;
+ dp[2][2] = 1;
+ // 상태 전이: 작은 부분 문제부터 점차 큰 부분 문제를 푼다
+ for (int i = 3; i <= n; i++) {
+ dp[i][1] = dp[i - 1][2];
+ dp[i][2] = dp[i - 2][1] + dp[i - 2][2];
+ }
+ int res = dp[n][1] + dp[n][2];
+ // 메모리 해제
+ for (int i = 0; i <= n; i++) {
+ free(dp[i]);
+ }
+ free(dp);
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 9;
+
+ int res = climbingStairsConstraintDP(n);
+ printf("%d개의 계단을 오르는 방법은 총 %d가지이다\n", n, res);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c b/ko/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c
new file mode 100644
index 000000000..ede8073c6
--- /dev/null
+++ b/ko/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c
@@ -0,0 +1,32 @@
+/**
+ * File: climbing_stairs_dfs.c
+ * Created Time: 2023-09-19
+ * Author: huawuque404 (huawuque404@163.com)
+ */
+
+#include "../utils/common.h"
+
+/* 탐색 */
+int dfs(int i) {
+ // dp[1]과 dp[2]는 이미 알고 있으므로 해당 값을 반환한다
+ if (i == 1 || i == 2)
+ return i;
+ // dp[i] = dp[i-1] + dp[i-2]
+ int count = dfs(i - 1) + dfs(i - 2);
+ return count;
+}
+
+/* 계단 오르기: 탐색 */
+int climbingStairsDFS(int n) {
+ return dfs(n);
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 9;
+
+ int res = climbingStairsDFS(n);
+ printf("%d개의 계단을 오르는 방법은 총 %d가지이다\n", n, res);
+
+ return 0;
+}
\ No newline at end of file
diff --git a/ko/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c b/ko/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c
new file mode 100644
index 000000000..cb5686a0a
--- /dev/null
+++ b/ko/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c
@@ -0,0 +1,44 @@
+/**
+ * File: climbing_stairs_dfs_mem.c
+ * Created Time: 2023-09-19
+ * Author: huawuque404 (huawuque404@163.com)
+ */
+
+#include "../utils/common.h"
+
+/* 메모이제이션을 이용한 탐색 */
+int dfs(int i, int *mem) {
+ // dp[1]과 dp[2]는 이미 알고 있으므로 해당 값을 반환한다
+ if (i == 1 || i == 2)
+ return i;
+ // dp[i]의 기록이 있으면 해당 값을 바로 반환한다
+ if (mem[i] != -1)
+ return mem[i];
+ // dp[i] = dp[i-1] + dp[i-2]
+ int count = dfs(i - 1, mem) + dfs(i - 2, mem);
+ // dp[i]를 기록한다
+ mem[i] = count;
+ return count;
+}
+
+/* 계단 오르기: 메모이제이션을 이용한 탐색 */
+int climbingStairsDFSMem(int n) {
+ // mem[i]는 i번째 계단까지 오르는 전체 방법의 수를 기록하며, -1은 기록이 없음을 나타낸다
+ int *mem = (int *)malloc((n + 1) * sizeof(int));
+ for (int i = 0; i <= n; i++) {
+ mem[i] = -1;
+ }
+ int result = dfs(n, mem);
+ free(mem);
+ return result;
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 9;
+
+ int res = climbingStairsDFSMem(n);
+ printf("%d개의 계단을 오르는 방법은 총 %d가지이다\n", n, res);
+
+ return 0;
+}
\ No newline at end of file
diff --git a/ko/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c b/ko/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c
new file mode 100644
index 000000000..812b7c723
--- /dev/null
+++ b/ko/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c
@@ -0,0 +1,51 @@
+/**
+ * File: climbing_stairs_dp.c
+ * Created Time: 2023-09-19
+ * Author: huawuque404 (huawuque404@163.com)
+ */
+
+#include "../utils/common.h"
+
+/* 계단 오르기: 동적 계획법 */
+int climbingStairsDP(int n) {
+ if (n == 1 || n == 2)
+ return n;
+ // 부분 문제의 해를 저장할 dp 테이블을 초기화한다
+ int *dp = (int *)malloc((n + 1) * sizeof(int));
+ // 초기 상태: 가장 작은 부분 문제의 해를 미리 설정한다
+ dp[1] = 1;
+ dp[2] = 2;
+ // 상태 전이: 작은 부분 문제부터 점차 큰 부분 문제를 푼다
+ for (int i = 3; i <= n; i++) {
+ dp[i] = dp[i - 1] + dp[i - 2];
+ }
+ int result = dp[n];
+ free(dp);
+ return result;
+}
+
+/* 계단 오르기: 공간을 최적화한 동적 계획법 */
+int climbingStairsDPComp(int n) {
+ if (n == 1 || n == 2)
+ return n;
+ int a = 1, b = 2;
+ for (int i = 3; i <= n; i++) {
+ int tmp = b;
+ b = a + b;
+ a = tmp;
+ }
+ return b;
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 9;
+
+ int res = climbingStairsDP(n);
+ printf("%d개의 계단을 오르는 방법은 총 %d가지이다\n", n, res);
+
+ res = climbingStairsDPComp(n);
+ printf("%d개의 계단을 오르는 방법은 총 %d가지이다\n", n, res);
+
+ return 0;
+}
\ No newline at end of file
diff --git a/ko/codes/c/chapter_dynamic_programming/coin_change.c b/ko/codes/c/chapter_dynamic_programming/coin_change.c
new file mode 100644
index 000000000..d01483d16
--- /dev/null
+++ b/ko/codes/c/chapter_dynamic_programming/coin_change.c
@@ -0,0 +1,92 @@
+/**
+ * File: coin_change.c
+ * Created Time: 2023-10-02
+ * Author: Zuoxun (845242523@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 최솟값 구하기 */
+int myMin(int a, int b) {
+ return a < b ? a : b;
+}
+
+/* 동전 교환: 동적 계획법 */
+int coinChangeDP(int coins[], int amt, int coinsSize) {
+ int n = coinsSize;
+ int MAX = amt + 1;
+ // dp 테이블을 초기화한다
+ int **dp = malloc((n + 1) * sizeof(int *));
+ for (int i = 0; i <= n; i++) {
+ dp[i] = calloc(amt + 1, sizeof(int));
+ }
+ // 상태 전이: 첫 번째 행과 첫 번째 열
+ for (int a = 1; a <= amt; a++) {
+ dp[0][a] = MAX;
+ }
+ // 상태 전이: 나머지 행과 열
+ for (int i = 1; i <= n; i++) {
+ for (int a = 1; a <= amt; a++) {
+ if (coins[i - 1] > a) {
+ // 목표 금액을 초과하면 동전 i를 선택하지 않는다
+ dp[i][a] = dp[i - 1][a];
+ } else {
+ // 동전 i를 선택하지 않는 경우와 선택하는 경우 중 더 작은 값
+ dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);
+ }
+ }
+ }
+ int res = dp[n][amt] != MAX ? dp[n][amt] : -1;
+ // 메모리 해제
+ for (int i = 0; i <= n; i++) {
+ free(dp[i]);
+ }
+ free(dp);
+ return res;
+}
+
+/* 동전 교환: 공간을 최적화한 동적 계획법 */
+int coinChangeDPComp(int coins[], int amt, int coinsSize) {
+ int n = coinsSize;
+ int MAX = amt + 1;
+ // dp 테이블을 초기화한다
+ int *dp = malloc((amt + 1) * sizeof(int));
+ for (int j = 1; j <= amt; j++) {
+ dp[j] = MAX;
+ }
+ dp[0] = 0;
+
+ // 상태 전이
+ for (int i = 1; i <= n; i++) {
+ for (int a = 1; a <= amt; a++) {
+ if (coins[i - 1] > a) {
+ // 목표 금액을 초과하면 동전 i를 선택하지 않는다
+ dp[a] = dp[a];
+ } else {
+ // 동전 i를 선택하지 않는 경우와 선택하는 경우 중 더 작은 값
+ dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1);
+ }
+ }
+ }
+ int res = dp[amt] != MAX ? dp[amt] : -1;
+ // 메모리 해제
+ free(dp);
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ int coins[] = {1, 2, 5};
+ int coinsSize = sizeof(coins) / sizeof(coins[0]);
+ int amt = 4;
+
+ // 동적 계획법
+ int res = coinChangeDP(coins, amt, coinsSize);
+ printf("목표 금액을 만드는 데 필요한 최소 동전 개수는 %d이다\n", res);
+
+ // 공간을 최적화한 동적 계획법
+ res = coinChangeDPComp(coins, amt, coinsSize);
+ printf("목표 금액을 만드는 데 필요한 최소 동전 개수는 %d이다\n", res);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_dynamic_programming/coin_change_ii.c b/ko/codes/c/chapter_dynamic_programming/coin_change_ii.c
new file mode 100644
index 000000000..7c7b62bf3
--- /dev/null
+++ b/ko/codes/c/chapter_dynamic_programming/coin_change_ii.c
@@ -0,0 +1,81 @@
+/**
+ * File: coin_change_ii.c
+ * Created Time: 2023-10-02
+ * Author: Zuoxun (845242523@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 동전 교환 II: 동적 계획법 */
+int coinChangeIIDP(int coins[], int amt, int coinsSize) {
+ int n = coinsSize;
+ // dp 테이블을 초기화한다
+ int **dp = malloc((n + 1) * sizeof(int *));
+ for (int i = 0; i <= n; i++) {
+ dp[i] = calloc(amt + 1, sizeof(int));
+ }
+ // 첫 번째 열을 초기화한다
+ for (int i = 0; i <= n; i++) {
+ dp[i][0] = 1;
+ }
+ // 상태 전이
+ for (int i = 1; i <= n; i++) {
+ for (int a = 1; a <= amt; a++) {
+ if (coins[i - 1] > a) {
+ // 목표 금액을 초과하면 동전 i를 선택하지 않는다
+ dp[i][a] = dp[i - 1][a];
+ } else {
+ // 동전 i를 선택하지 않는 경우와 선택하는 경우의 방법 수를 더한다
+ dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];
+ }
+ }
+ }
+ int res = dp[n][amt];
+ // 메모리 해제
+ for (int i = 0; i <= n; i++) {
+ free(dp[i]);
+ }
+ free(dp);
+ return res;
+}
+
+/* 동전 교환 II: 공간을 최적화한 동적 계획법 */
+int coinChangeIIDPComp(int coins[], int amt, int coinsSize) {
+ int n = coinsSize;
+ // dp 테이블을 초기화한다
+ int *dp = calloc(amt + 1, sizeof(int));
+ dp[0] = 1;
+ // 상태 전이
+ for (int i = 1; i <= n; i++) {
+ for (int a = 1; a <= amt; a++) {
+ if (coins[i - 1] > a) {
+ // 목표 금액을 초과하면 동전 i를 선택하지 않는다
+ dp[a] = dp[a];
+ } else {
+ // 동전 i를 선택하지 않는 경우와 선택하는 경우의 방법 수를 더한다
+ dp[a] = dp[a] + dp[a - coins[i - 1]];
+ }
+ }
+ }
+ int res = dp[amt];
+ // 메모리 해제
+ free(dp);
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ int coins[] = {1, 2, 5};
+ int coinsSize = sizeof(coins) / sizeof(coins[0]);
+ int amt = 5;
+
+ // 동적 계획법
+ int res = coinChangeIIDP(coins, amt, coinsSize);
+ printf("목표 금액을 만드는 동전 조합의 수는 %d이다\n", res);
+
+ // 공간을 최적화한 동적 계획법
+ res = coinChangeIIDPComp(coins, amt, coinsSize);
+ printf("목표 금액을 만드는 동전 조합의 수는 %d이다\n", res);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_dynamic_programming/edit_distance.c b/ko/codes/c/chapter_dynamic_programming/edit_distance.c
new file mode 100644
index 000000000..bc74481ff
--- /dev/null
+++ b/ko/codes/c/chapter_dynamic_programming/edit_distance.c
@@ -0,0 +1,159 @@
+/**
+ * File: edit_distance.c
+ * Created Time: 2023-10-02
+ * Author: Zuoxun (845242523@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 최솟값 구하기 */
+int myMin(int a, int b) {
+ return a < b ? a : b;
+}
+
+/* 편집 거리: 완전 탐색 */
+int editDistanceDFS(char *s, char *t, int i, int j) {
+ // s와 t가 모두 비어 있으면 0을 반환한다
+ if (i == 0 && j == 0)
+ return 0;
+ // s가 비어 있으면 t의 길이를 반환한다
+ if (i == 0)
+ return j;
+ // t가 비어 있으면 s의 길이를 반환한다
+ if (j == 0)
+ return i;
+ // 두 문자가 같으면 두 문자를 바로 건너뛴다
+ if (s[i - 1] == t[j - 1])
+ return editDistanceDFS(s, t, i - 1, j - 1);
+ // 최소 편집 횟수 = 삽입, 삭제, 교체 세 연산에 대한 최소 편집 횟수 + 1
+ int insert = editDistanceDFS(s, t, i, j - 1);
+ int del = editDistanceDFS(s, t, i - 1, j);
+ int replace = editDistanceDFS(s, t, i - 1, j - 1);
+ // 최소 편집 횟수를 반환한다
+ return myMin(myMin(insert, del), replace) + 1;
+}
+
+/* 편집 거리: 메모이제이션을 이용한 탐색 */
+int editDistanceDFSMem(char *s, char *t, int memCols, int **mem, int i, int j) {
+ // s와 t가 모두 비어 있으면 0을 반환한다
+ if (i == 0 && j == 0)
+ return 0;
+ // s가 비어 있으면 t의 길이를 반환한다
+ if (i == 0)
+ return j;
+ // t가 비어 있으면 s의 길이를 반환한다
+ if (j == 0)
+ return i;
+ // 기록이 이미 있으면 해당 값을 바로 반환한다
+ if (mem[i][j] != -1)
+ return mem[i][j];
+ // 두 문자가 같으면 두 문자를 바로 건너뛴다
+ if (s[i - 1] == t[j - 1])
+ return editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1);
+ // 최소 편집 횟수 = 삽입, 삭제, 교체 세 연산에 대한 최소 편집 횟수 + 1
+ int insert = editDistanceDFSMem(s, t, memCols, mem, i, j - 1);
+ int del = editDistanceDFSMem(s, t, memCols, mem, i - 1, j);
+ int replace = editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1);
+ // 최소 편집 횟수를 기록하고 반환한다
+ mem[i][j] = myMin(myMin(insert, del), replace) + 1;
+ return mem[i][j];
+}
+
+/* 편집 거리: 동적 계획법 */
+int editDistanceDP(char *s, char *t, int n, int m) {
+ int **dp = malloc((n + 1) * sizeof(int *));
+ for (int i = 0; i <= n; i++) {
+ dp[i] = calloc(m + 1, sizeof(int));
+ }
+ // 상태 전이: 첫 번째 행과 첫 번째 열
+ for (int i = 1; i <= n; i++) {
+ dp[i][0] = i;
+ }
+ for (int j = 1; j <= m; j++) {
+ dp[0][j] = j;
+ }
+ // 상태 전이: 나머지 행과 열
+ for (int i = 1; i <= n; i++) {
+ for (int j = 1; j <= m; j++) {
+ if (s[i - 1] == t[j - 1]) {
+ // 두 문자가 같으면 두 문자를 바로 건너뛴다
+ dp[i][j] = dp[i - 1][j - 1];
+ } else {
+ // 최소 편집 횟수 = 삽입, 삭제, 교체 세 연산에 대한 최소 편집 횟수 + 1
+ dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;
+ }
+ }
+ }
+ int res = dp[n][m];
+ // 메모리 해제
+ for (int i = 0; i <= n; i++) {
+ free(dp[i]);
+ }
+ return res;
+}
+
+/* 편집 거리: 공간을 최적화한 동적 계획법 */
+int editDistanceDPComp(char *s, char *t, int n, int m) {
+ int *dp = calloc(m + 1, sizeof(int));
+ // 상태 전이: 첫 번째 행
+ for (int j = 1; j <= m; j++) {
+ dp[j] = j;
+ }
+ // 상태 전이: 나머지 행
+ for (int i = 1; i <= n; i++) {
+ // 상태 전이: 첫 번째 열
+ int leftup = dp[0]; // dp[i-1, j-1]을 임시로 저장한다
+ dp[0] = i;
+ // 상태 전이: 나머지 열
+ for (int j = 1; j <= m; j++) {
+ int temp = dp[j];
+ if (s[i - 1] == t[j - 1]) {
+ // 두 문자가 같으면 두 문자를 바로 건너뛴다
+ dp[j] = leftup;
+ } else {
+ // 최소 편집 횟수 = 삽입, 삭제, 교체 세 연산에 대한 최소 편집 횟수 + 1
+ dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1;
+ }
+ leftup = temp; // 다음 반복의 dp[i-1, j-1]로 갱신한다
+ }
+ }
+ int res = dp[m];
+ // 메모리 해제
+ free(dp);
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ char *s = "bag";
+ char *t = "pack";
+ int n = strlen(s), m = strlen(t);
+
+ // 완전 탐색
+ int res = editDistanceDFS(s, t, n, m);
+ printf("%s에서 %s로 바꾸려면 최소 %d회의 편집이 필요하다\n", s, t, res);
+
+ // 메모이제이션을 이용한 탐색
+ int **mem = malloc((n + 1) * sizeof(int *));
+ for (int i = 0; i <= n; i++) {
+ mem[i] = malloc((m + 1) * sizeof(int));
+ memset(mem[i], -1, (m + 1) * sizeof(int));
+ }
+ res = editDistanceDFSMem(s, t, m + 1, mem, n, m);
+ printf("%s에서 %s로 바꾸려면 최소 %d회의 편집이 필요하다\n", s, t, res);
+ // 메모리 해제
+ for (int i = 0; i <= n; i++) {
+ free(mem[i]);
+ }
+ free(mem);
+
+ // 동적 계획법
+ res = editDistanceDP(s, t, n, m);
+ printf("%s에서 %s로 바꾸려면 최소 %d회의 편집이 필요하다\n", s, t, res);
+
+ // 공간을 최적화한 동적 계획법
+ res = editDistanceDPComp(s, t, n, m);
+ printf("%s에서 %s로 바꾸려면 최소 %d회의 편집이 필요하다\n", s, t, res);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_dynamic_programming/knapsack.c b/ko/codes/c/chapter_dynamic_programming/knapsack.c
new file mode 100644
index 000000000..9302828b4
--- /dev/null
+++ b/ko/codes/c/chapter_dynamic_programming/knapsack.c
@@ -0,0 +1,137 @@
+/**
+ * File: knapsack.c
+ * Created Time: 2023-10-02
+ * Author: Zuoxun (845242523@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 최댓값 구하기 */
+int myMax(int a, int b) {
+ return a > b ? a : b;
+}
+
+/* 0-1 배낭: 완전 탐색 */
+int knapsackDFS(int wgt[], int val[], int i, int c) {
+ // 모든 물건의 선택을 마쳤거나 배낭에 남은 용량이 없으면 가치 0을 반환한다
+ if (i == 0 || c == 0) {
+ return 0;
+ }
+ // 배낭 용량을 초과하면 배낭에 넣지 않는 선택만 가능하다
+ if (wgt[i - 1] > c) {
+ return knapsackDFS(wgt, val, i - 1, c);
+ }
+ // 물건 i를 넣지 않는 경우와 넣는 경우의 최대 가치를 계산한다
+ int no = knapsackDFS(wgt, val, i - 1, c);
+ int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];
+ // 두 경우 중 더 큰 가치를 반환한다
+ return myMax(no, yes);
+}
+
+/* 0-1 배낭: 메모이제이션을 이용한 탐색 */
+int knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) {
+ // 모든 물건의 선택을 마쳤거나 배낭에 남은 용량이 없으면 가치 0을 반환한다
+ if (i == 0 || c == 0) {
+ return 0;
+ }
+ // 기록이 이미 있으면 바로 반환한다
+ if (mem[i][c] != -1) {
+ return mem[i][c];
+ }
+ // 배낭 용량을 초과하면 배낭에 넣지 않는 선택만 가능하다
+ if (wgt[i - 1] > c) {
+ return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c);
+ }
+ // 물건 i를 넣지 않는 경우와 넣는 경우의 최대 가치를 계산한다
+ int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c);
+ int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1];
+ // 두 경우 중 더 큰 가치를 기록하고 반환한다
+ mem[i][c] = myMax(no, yes);
+ return mem[i][c];
+}
+
+/* 0-1 배낭: 동적 계획법 */
+int knapsackDP(int wgt[], int val[], int cap, int wgtSize) {
+ int n = wgtSize;
+ // dp 테이블을 초기화한다
+ int **dp = malloc((n + 1) * sizeof(int *));
+ for (int i = 0; i <= n; i++) {
+ dp[i] = calloc(cap + 1, sizeof(int));
+ }
+ // 상태 전이
+ for (int i = 1; i <= n; i++) {
+ for (int c = 1; c <= cap; c++) {
+ if (wgt[i - 1] > c) {
+ // 배낭 용량을 초과하면 물건 i를 선택하지 않는다
+ dp[i][c] = dp[i - 1][c];
+ } else {
+ // 물건 i를 선택하지 않는 경우와 선택하는 경우 중 더 큰 값
+ dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);
+ }
+ }
+ }
+ int res = dp[n][cap];
+ // 메모리 해제
+ for (int i = 0; i <= n; i++) {
+ free(dp[i]);
+ }
+ return res;
+}
+
+/* 0-1 배낭: 공간을 최적화한 동적 계획법 */
+int knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) {
+ int n = wgtSize;
+ // dp 테이블을 초기화한다
+ int *dp = calloc(cap + 1, sizeof(int));
+ // 상태 전이
+ for (int i = 1; i <= n; i++) {
+ // 역방향 순회
+ for (int c = cap; c >= 1; c--) {
+ if (wgt[i - 1] <= c) {
+ // 물건 i를 선택하지 않는 경우와 선택하는 경우 중 더 큰 값
+ dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);
+ }
+ }
+ }
+ int res = dp[cap];
+ // 메모리 해제
+ free(dp);
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ int wgt[] = {10, 20, 30, 40, 50};
+ int val[] = {50, 120, 150, 210, 240};
+ int cap = 50;
+ int n = sizeof(wgt) / sizeof(wgt[0]);
+ int wgtSize = n;
+
+ // 완전 탐색
+ int res = knapsackDFS(wgt, val, n, cap);
+ printf("배낭 용량을 넘지 않는 물건의 최대 가치는 %d이다\n", res);
+
+ // 메모이제이션을 이용한 탐색
+ int **mem = malloc((n + 1) * sizeof(int *));
+ for (int i = 0; i <= n; i++) {
+ mem[i] = malloc((cap + 1) * sizeof(int));
+ memset(mem[i], -1, (cap + 1) * sizeof(int));
+ }
+ res = knapsackDFSMem(wgt, val, cap + 1, mem, n, cap);
+ printf("배낭 용량을 넘지 않는 물건의 최대 가치는 %d이다\n", res);
+ // 메모리 해제
+ for (int i = 0; i <= n; i++) {
+ free(mem[i]);
+ }
+ free(mem);
+
+ // 동적 계획법
+ res = knapsackDP(wgt, val, cap, wgtSize);
+ printf("배낭 용량을 넘지 않는 물건의 최대 가치는 %d이다\n", res);
+
+ // 공간을 최적화한 동적 계획법
+ res = knapsackDPComp(wgt, val, cap, wgtSize);
+ printf("배낭 용량을 넘지 않는 물건의 최대 가치는 %d이다\n", res);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c b/ko/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c
new file mode 100644
index 000000000..8cb343fd6
--- /dev/null
+++ b/ko/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c
@@ -0,0 +1,62 @@
+/**
+ * File: min_cost_climbing_stairs_dp.c
+ * Created Time: 2023-10-02
+ * Author: Zuoxun (845242523@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 최솟값 구하기 */
+int myMin(int a, int b) {
+ return a < b ? a : b;
+}
+
+/* 계단 오르기의 최소 비용: 동적 계획법 */
+int minCostClimbingStairsDP(int cost[], int costSize) {
+ int n = costSize - 1;
+ if (n == 1 || n == 2)
+ return cost[n];
+ // 부분 문제의 해를 저장할 dp 테이블을 초기화한다
+ int *dp = calloc(n + 1, sizeof(int));
+ // 초기 상태: 가장 작은 부분 문제의 해를 미리 설정한다
+ dp[1] = cost[1];
+ dp[2] = cost[2];
+ // 상태 전이: 작은 부분 문제부터 점차 큰 부분 문제를 푼다
+ for (int i = 3; i <= n; i++) {
+ dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i];
+ }
+ int res = dp[n];
+ // 메모리 해제
+ free(dp);
+ return res;
+}
+
+/* 계단 오르기의 최소 비용: 공간을 최적화한 동적 계획법 */
+int minCostClimbingStairsDPComp(int cost[], int costSize) {
+ int n = costSize - 1;
+ if (n == 1 || n == 2)
+ return cost[n];
+ int a = cost[1], b = cost[2];
+ for (int i = 3; i <= n; i++) {
+ int tmp = b;
+ b = myMin(a, tmp) + cost[i];
+ a = tmp;
+ }
+ return b;
+}
+
+/* 실행 코드 */
+int main() {
+ int cost[] = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1};
+ int costSize = sizeof(cost) / sizeof(cost[0]);
+ printf("입력한 계단의 비용 리스트:");
+ printArray(cost, costSize);
+
+ int res = minCostClimbingStairsDP(cost, costSize);
+ printf("계단을 끝까지 오르는 최소 비용은 %d이다\n", res);
+
+ res = minCostClimbingStairsDPComp(cost, costSize);
+ printf("계단을 끝까지 오르는 최소 비용은 %d이다\n", res);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_dynamic_programming/min_path_sum.c b/ko/codes/c/chapter_dynamic_programming/min_path_sum.c
new file mode 100644
index 000000000..efbd92da2
--- /dev/null
+++ b/ko/codes/c/chapter_dynamic_programming/min_path_sum.c
@@ -0,0 +1,134 @@
+/**
+ * File: min_path_sum.c
+ * Created Time: 2023-10-02
+ * Author: Zuoxun (845242523@qq.com)
+ */
+
+#include "../utils/common.h"
+
+// 행렬의 최대 행과 열 개수가 100이라고 가정한다
+#define MAX_SIZE 100
+
+/* 최솟값 구하기 */
+int myMin(int a, int b) {
+ return a < b ? a : b;
+}
+
+/* 최소 경로 합: 완전 탐색 */
+int minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) {
+ // 왼쪽 위 모서리의 칸이면 탐색을 종료한다
+ if (i == 0 && j == 0) {
+ return grid[0][0];
+ }
+ // 행 또는 열 인덱스가 범위를 벗어나면 비용 +∞를 반환한다
+ if (i < 0 || j < 0) {
+ return INT_MAX;
+ }
+ // 왼쪽 위 모서리에서 (i-1, j)와 (i, j-1)까지의 최소 경로 비용을 계산한다
+ int up = minPathSumDFS(grid, i - 1, j);
+ int left = minPathSumDFS(grid, i, j - 1);
+ // 왼쪽 위 모서리에서 (i, j)까지의 최소 경로 비용을 반환한다
+ return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX;
+}
+
+/* 최소 경로 합: 메모이제이션을 이용한 탐색 */
+int minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) {
+ // 왼쪽 위 모서리의 칸이면 탐색을 종료한다
+ if (i == 0 && j == 0) {
+ return grid[0][0];
+ }
+ // 행 또는 열 인덱스가 범위를 벗어나면 비용 +∞를 반환한다
+ if (i < 0 || j < 0) {
+ return INT_MAX;
+ }
+ // 기록이 이미 있으면 바로 반환한다
+ if (mem[i][j] != -1) {
+ return mem[i][j];
+ }
+ // 왼쪽 칸과 위쪽 칸까지의 최소 경로 비용
+ int up = minPathSumDFSMem(grid, mem, i - 1, j);
+ int left = minPathSumDFSMem(grid, mem, i, j - 1);
+ // 왼쪽 위 모서리에서 (i, j)까지의 최소 경로 비용을 기록하고 반환한다
+ mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX;
+ return mem[i][j];
+}
+
+/* 최소 경로 합: 동적 계획법 */
+int minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) {
+ // dp 테이블을 초기화한다
+ int **dp = malloc(n * sizeof(int *));
+ for (int i = 0; i < n; i++) {
+ dp[i] = calloc(m, sizeof(int));
+ }
+ dp[0][0] = grid[0][0];
+ // 상태 전이: 첫 번째 행
+ for (int j = 1; j < m; j++) {
+ dp[0][j] = dp[0][j - 1] + grid[0][j];
+ }
+ // 상태 전이: 첫 번째 열
+ for (int i = 1; i < n; i++) {
+ dp[i][0] = dp[i - 1][0] + grid[i][0];
+ }
+ // 상태 전이: 나머지 행과 열
+ for (int i = 1; i < n; i++) {
+ for (int j = 1; j < m; j++) {
+ dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];
+ }
+ }
+ int res = dp[n - 1][m - 1];
+ // 메모리 해제
+ for (int i = 0; i < n; i++) {
+ free(dp[i]);
+ }
+ return res;
+}
+
+/* 최소 경로 합: 공간을 최적화한 동적 계획법 */
+int minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) {
+ // dp 테이블을 초기화한다
+ int *dp = calloc(m, sizeof(int));
+ // 상태 전이: 첫 번째 행
+ dp[0] = grid[0][0];
+ for (int j = 1; j < m; j++) {
+ dp[j] = dp[j - 1] + grid[0][j];
+ }
+ // 상태 전이: 나머지 행
+ for (int i = 1; i < n; i++) {
+ // 상태 전이: 첫 번째 열
+ dp[0] = dp[0] + grid[i][0];
+ // 상태 전이: 나머지 열
+ for (int j = 1; j < m; j++) {
+ dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j];
+ }
+ }
+ int res = dp[m - 1];
+ // 메모리 해제
+ free(dp);
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ int grid[MAX_SIZE][MAX_SIZE] = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}};
+ int n = 4, m = 4; // 행렬의 용량은 MAX_SIZE * MAX_SIZE이며, 실제 사용하는 행과 열의 크기는 n * m이다
+
+ // 완전 탐색
+ int res = minPathSumDFS(grid, n - 1, m - 1);
+ printf("왼쪽 위에서 오른쪽 아래까지의 최소 경로 합은 %d이다\n", res);
+
+ // 메모이제이션을 이용한 탐색
+ int mem[MAX_SIZE][MAX_SIZE];
+ memset(mem, -1, sizeof(mem));
+ res = minPathSumDFSMem(grid, mem, n - 1, m - 1);
+ printf("왼쪽 위에서 오른쪽 아래까지의 최소 경로 합은 %d이다\n", res);
+
+ // 동적 계획법
+ res = minPathSumDP(grid, n, m);
+ printf("왼쪽 위에서 오른쪽 아래까지의 최소 경로 합은 %d이다\n", res);
+
+ // 공간을 최적화한 동적 계획법
+ res = minPathSumDPComp(grid, n, m);
+ printf("왼쪽 위에서 오른쪽 아래까지의 최소 경로 합은 %d이다\n", res);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_dynamic_programming/unbounded_knapsack.c b/ko/codes/c/chapter_dynamic_programming/unbounded_knapsack.c
new file mode 100644
index 000000000..05e1d2213
--- /dev/null
+++ b/ko/codes/c/chapter_dynamic_programming/unbounded_knapsack.c
@@ -0,0 +1,81 @@
+/**
+ * File: unbounded_knapsack.c
+ * Created Time: 2023-10-02
+ * Author: Zuoxun (845242523@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 최댓값 구하기 */
+int myMax(int a, int b) {
+ return a > b ? a : b;
+}
+
+/* 무한 배낭: 동적 계획법 */
+int unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) {
+ int n = wgtSize;
+ // dp 테이블을 초기화한다
+ int **dp = malloc((n + 1) * sizeof(int *));
+ for (int i = 0; i <= n; i++) {
+ dp[i] = calloc(cap + 1, sizeof(int));
+ }
+ // 상태 전이
+ for (int i = 1; i <= n; i++) {
+ for (int c = 1; c <= cap; c++) {
+ if (wgt[i - 1] > c) {
+ // 배낭 용량을 초과하면 물건 i를 선택하지 않는다
+ dp[i][c] = dp[i - 1][c];
+ } else {
+ // 물건 i를 선택하지 않는 경우와 선택하는 경우 중 더 큰 값
+ dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);
+ }
+ }
+ }
+ int res = dp[n][cap];
+ // 메모리 해제
+ for (int i = 0; i <= n; i++) {
+ free(dp[i]);
+ }
+ return res;
+}
+
+/* 무한 배낭: 공간을 최적화한 동적 계획법 */
+int unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) {
+ int n = wgtSize;
+ // dp 테이블을 초기화한다
+ int *dp = calloc(cap + 1, sizeof(int));
+ // 상태 전이
+ for (int i = 1; i <= n; i++) {
+ for (int c = 1; c <= cap; c++) {
+ if (wgt[i - 1] > c) {
+ // 배낭 용량을 초과하면 물건 i를 선택하지 않는다
+ dp[c] = dp[c];
+ } else {
+ // 물건 i를 선택하지 않는 경우와 선택하는 경우 중 더 큰 값
+ dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);
+ }
+ }
+ }
+ int res = dp[cap];
+ // 메모리 해제
+ free(dp);
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ int wgt[] = {1, 2, 3};
+ int val[] = {5, 11, 15};
+ int wgtSize = sizeof(wgt) / sizeof(wgt[0]);
+ int cap = 4;
+
+ // 동적 계획법
+ int res = unboundedKnapsackDP(wgt, val, cap, wgtSize);
+ printf("배낭 용량을 넘지 않는 물건의 최대 가치는 %d이다\n", res);
+
+ // 공간을 최적화한 동적 계획법
+ res = unboundedKnapsackDPComp(wgt, val, cap, wgtSize);
+ printf("배낭 용량을 넘지 않는 물건의 최대 가치는 %d이다\n", res);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_graph/CMakeLists.txt b/ko/codes/c/chapter_graph/CMakeLists.txt
new file mode 100644
index 000000000..28f8470f4
--- /dev/null
+++ b/ko/codes/c/chapter_graph/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_executable(graph_adjacency_matrix graph_adjacency_matrix.c)
+add_executable(graph_adjacency_list_test graph_adjacency_list_test.c)
+add_executable(graph_bfs graph_bfs.c)
+add_executable(graph_dfs graph_dfs.c)
diff --git a/ko/codes/c/chapter_graph/graph_adjacency_list.c b/ko/codes/c/chapter_graph/graph_adjacency_list.c
new file mode 100644
index 000000000..1eb33643a
--- /dev/null
+++ b/ko/codes/c/chapter_graph/graph_adjacency_list.c
@@ -0,0 +1,171 @@
+/**
+ * File: graph_adjacency_list.c
+ * Created Time: 2023-07-07
+ * Author: NI-SW (947743645@qq.com)
+ */
+
+#include "../utils/common.h"
+
+// 노드가 최대 100개라고 가정한다
+#define MAX_SIZE 100
+
+/* 노드 구조체 */
+typedef struct AdjListNode {
+ Vertex *vertex; // 정점
+ struct AdjListNode *next; // 다음 노드
+} AdjListNode;
+
+/* 인접 리스트로 구현한 무방향 그래프 클래스 */
+typedef struct {
+ AdjListNode *heads[MAX_SIZE]; // 노드 배열
+ int size; // 노드 개수
+} GraphAdjList;
+
+/* 생성자 */
+GraphAdjList *newGraphAdjList() {
+ GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList));
+ if (!graph) {
+ return NULL;
+ }
+ graph->size = 0;
+ for (int i = 0; i < MAX_SIZE; i++) {
+ graph->heads[i] = NULL;
+ }
+ return graph;
+}
+
+/* 소멸자 */
+void delGraphAdjList(GraphAdjList *graph) {
+ for (int i = 0; i < graph->size; i++) {
+ AdjListNode *cur = graph->heads[i];
+ while (cur != NULL) {
+ AdjListNode *next = cur->next;
+ if (cur != graph->heads[i]) {
+ free(cur);
+ }
+ cur = next;
+ }
+ free(graph->heads[i]->vertex);
+ free(graph->heads[i]);
+ }
+ free(graph);
+}
+
+/* 정점에 해당하는 노드 찾기 */
+AdjListNode *findNode(GraphAdjList *graph, Vertex *vet) {
+ for (int i = 0; i < graph->size; i++) {
+ if (graph->heads[i]->vertex == vet) {
+ return graph->heads[i];
+ }
+ }
+ return NULL;
+}
+
+/* 간선 추가 보조 함수 */
+void addEdgeHelper(AdjListNode *head, Vertex *vet) {
+ AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode));
+ node->vertex = vet;
+ // 맨 앞에 삽입한다
+ node->next = head->next;
+ head->next = node;
+}
+
+/* 간선 추가 */
+void addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) {
+ AdjListNode *head1 = findNode(graph, vet1);
+ AdjListNode *head2 = findNode(graph, vet2);
+ assert(head1 != NULL && head2 != NULL && head1 != head2);
+ // 간선 vet1 - vet2를 추가한다
+ addEdgeHelper(head1, vet2);
+ addEdgeHelper(head2, vet1);
+}
+
+/* 간선 삭제 보조 함수 */
+void removeEdgeHelper(AdjListNode *head, Vertex *vet) {
+ AdjListNode *pre = head;
+ AdjListNode *cur = head->next;
+ // 연결 리스트에서 vet에 해당하는 노드를 찾는다
+ while (cur != NULL && cur->vertex != vet) {
+ pre = cur;
+ cur = cur->next;
+ }
+ if (cur == NULL)
+ return;
+ // 연결 리스트에서 vet에 해당하는 노드를 삭제한다
+ pre->next = cur->next;
+ // 메모리 해제
+ free(cur);
+}
+
+/* 간선 삭제 */
+void removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) {
+ AdjListNode *head1 = findNode(graph, vet1);
+ AdjListNode *head2 = findNode(graph, vet2);
+ assert(head1 != NULL && head2 != NULL);
+ // 간선 vet1 - vet2를 삭제한다
+ removeEdgeHelper(head1, head2->vertex);
+ removeEdgeHelper(head2, head1->vertex);
+}
+
+/* 정점 추가 */
+void addVertex(GraphAdjList *graph, Vertex *vet) {
+ assert(graph != NULL && graph->size < MAX_SIZE);
+ AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode));
+ head->vertex = vet;
+ head->next = NULL;
+ // 인접 리스트에 새 연결 리스트를 추가한다
+ graph->heads[graph->size++] = head;
+}
+
+/* 정점 삭제 */
+void removeVertex(GraphAdjList *graph, Vertex *vet) {
+ AdjListNode *node = findNode(graph, vet);
+ assert(node != NULL);
+ // 인접 리스트에서 정점 vet에 해당하는 연결 리스트를 삭제한다
+ AdjListNode *cur = node, *pre = NULL;
+ while (cur) {
+ pre = cur;
+ cur = cur->next;
+ free(pre);
+ }
+ // 다른 정점의 연결 리스트를 순회하여 vet를 포함하는 모든 간선을 삭제한다
+ for (int i = 0; i < graph->size; i++) {
+ cur = graph->heads[i];
+ pre = NULL;
+ while (cur) {
+ pre = cur;
+ cur = cur->next;
+ if (cur && cur->vertex == vet) {
+ pre->next = cur->next;
+ free(cur);
+ break;
+ }
+ }
+ }
+ // 해당 정점 뒤의 정점을 앞으로 옮겨 빈자리를 채운다
+ int i;
+ for (i = 0; i < graph->size; i++) {
+ if (graph->heads[i] == node)
+ break;
+ }
+ for (int j = i; j < graph->size - 1; j++) {
+ graph->heads[j] = graph->heads[j + 1];
+ }
+ graph->size--;
+ free(vet);
+}
+
+/* 인접 리스트 출력 */
+void printGraph(const GraphAdjList *graph) {
+ printf("인접 리스트 =\n");
+ for (int i = 0; i < graph->size; ++i) {
+ AdjListNode *node = graph->heads[i];
+ printf("%d: [", node->vertex->val);
+ node = node->next;
+ while (node) {
+ printf("%d, ", node->vertex->val);
+ node = node->next;
+ }
+ printf("]\n");
+ }
+}
diff --git a/ko/codes/c/chapter_graph/graph_adjacency_list_test.c b/ko/codes/c/chapter_graph/graph_adjacency_list_test.c
new file mode 100644
index 000000000..67380000c
--- /dev/null
+++ b/ko/codes/c/chapter_graph/graph_adjacency_list_test.c
@@ -0,0 +1,55 @@
+/**
+ * File: graph_adjacency_list_test.c
+ * Created Time: 2023-07-11
+ * Author: NI-SW (947743645@qq.com)
+ */
+
+#include "graph_adjacency_list.c"
+
+/* 실행 코드 */
+int main() {
+ int vals[] = {1, 3, 2, 5, 4};
+ int size = sizeof(vals) / sizeof(vals[0]);
+ Vertex **v = valsToVets(vals, size);
+ Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}};
+ int egdeSize = sizeof(edges) / sizeof(edges[0]);
+ GraphAdjList *graph = newGraphAdjList();
+ // 모든 정점과 간선을 추가한다
+ for (int i = 0; i < size; i++) {
+ addVertex(graph, v[i]);
+ }
+ for (int i = 0; i < egdeSize; i++) {
+ addEdge(graph, edges[i][0], edges[i][1]);
+ }
+ printf("\n초기화한 그래프:\n");
+ printGraph(graph);
+
+ /* 간선 추가 */
+ // 정점 1, 2는 각각 v[0], v[2]이다
+ addEdge(graph, v[0], v[2]);
+ printf("\n간선 1-2를 추가한 후의 그래프:\n");
+ printGraph(graph);
+
+ /* 간선 삭제 */
+ // 정점 1, 3은 각각 v[0], v[1]이다
+ removeEdge(graph, v[0], v[1]);
+ printf("\n간선 1-3을 삭제한 후의 그래프:\n");
+ printGraph(graph);
+
+ /* 정점 추가 */
+ Vertex *v5 = newVertex(6);
+ addVertex(graph, v5);
+ printf("\n정점 6을 추가한 후의 그래프:\n");
+ printGraph(graph);
+
+ /* 정점 삭제 */
+ // 정점 3은 v[1]이다
+ removeVertex(graph, v[1]);
+ printf("\n정점 3을 삭제한 후의 그래프:\n");
+ printGraph(graph);
+
+ // 메모리 해제
+ delGraphAdjList(graph);
+ free(v);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_graph/graph_adjacency_matrix.c b/ko/codes/c/chapter_graph/graph_adjacency_matrix.c
new file mode 100644
index 000000000..e47f46484
--- /dev/null
+++ b/ko/codes/c/chapter_graph/graph_adjacency_matrix.c
@@ -0,0 +1,150 @@
+/**
+ * File: graph_adjacency_matrix.c
+ * Created Time: 2023-07-06
+ * Author: NI-SW (947743645@qq.com)
+ */
+
+#include "../utils/common.h"
+
+// 정점이 최대 100개라고 가정한다
+#define MAX_SIZE 100
+
+/* 인접 행렬로 구현한 무방향 그래프 구조체 */
+typedef struct {
+ int vertices[MAX_SIZE];
+ int adjMat[MAX_SIZE][MAX_SIZE];
+ int size;
+} GraphAdjMat;
+
+/* 생성자 */
+GraphAdjMat *newGraphAdjMat() {
+ GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat));
+ graph->size = 0;
+ for (int i = 0; i < MAX_SIZE; i++) {
+ for (int j = 0; j < MAX_SIZE; j++) {
+ graph->adjMat[i][j] = 0;
+ }
+ }
+ return graph;
+}
+
+/* 소멸자 */
+void delGraphAdjMat(GraphAdjMat *graph) {
+ free(graph);
+}
+
+/* 정점 추가 */
+void addVertex(GraphAdjMat *graph, int val) {
+ if (graph->size == MAX_SIZE) {
+ fprintf(stderr, "그래프의 정점 개수가 최댓값에 도달했다\n");
+ return;
+ }
+ // n번째 정점을 추가하고 n번째 행과 열을 0으로 설정한다
+ int n = graph->size;
+ graph->vertices[n] = val;
+ for (int i = 0; i <= n; i++) {
+ graph->adjMat[n][i] = graph->adjMat[i][n] = 0;
+ }
+ graph->size++;
+}
+
+/* 정점 삭제 */
+void removeVertex(GraphAdjMat *graph, int index) {
+ if (index < 0 || index >= graph->size) {
+ fprintf(stderr, "정점 인덱스가 범위를 벗어났다\n");
+ return;
+ }
+ // 정점 리스트에서 인덱스 index의 정점을 제거한다
+ for (int i = index; i < graph->size - 1; i++) {
+ graph->vertices[i] = graph->vertices[i + 1];
+ }
+ // 인접 행렬에서 인덱스 index의 행을 삭제한다
+ for (int i = index; i < graph->size - 1; i++) {
+ for (int j = 0; j < graph->size; j++) {
+ graph->adjMat[i][j] = graph->adjMat[i + 1][j];
+ }
+ }
+ // 인접 행렬에서 인덱스 index의 열을 삭제한다
+ for (int i = 0; i < graph->size; i++) {
+ for (int j = index; j < graph->size - 1; j++) {
+ graph->adjMat[i][j] = graph->adjMat[i][j + 1];
+ }
+ }
+ graph->size--;
+}
+
+/* 간선 추가 */
+// 매개변수 i, j는 vertices 원소의 인덱스에 해당한다
+void addEdge(GraphAdjMat *graph, int i, int j) {
+ if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) {
+ fprintf(stderr, "간선 인덱스가 범위를 벗어나거나 서로 같다\n");
+ return;
+ }
+ graph->adjMat[i][j] = 1;
+ graph->adjMat[j][i] = 1;
+}
+
+/* 간선 삭제 */
+// 매개변수 i, j는 vertices 원소의 인덱스에 해당한다
+void removeEdge(GraphAdjMat *graph, int i, int j) {
+ if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) {
+ fprintf(stderr, "간선 인덱스가 범위를 벗어나거나 서로 같다\n");
+ return;
+ }
+ graph->adjMat[i][j] = 0;
+ graph->adjMat[j][i] = 0;
+}
+
+/* 인접 행렬 출력 */
+void printGraphAdjMat(GraphAdjMat *graph) {
+ printf("정점 리스트 = ");
+ printArray(graph->vertices, graph->size);
+ printf("인접 행렬 =\n");
+ for (int i = 0; i < graph->size; i++) {
+ printArray(graph->adjMat[i], graph->size);
+ }
+}
+
+/* 실행 코드 */
+int main() {
+ // 무방향 그래프 초기화
+ GraphAdjMat *graph = newGraphAdjMat();
+ int vertices[] = {1, 3, 2, 5, 4};
+ for (int i = 0; i < 5; i++) {
+ addVertex(graph, vertices[i]);
+ }
+ int edges[][2] = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}};
+ for (int i = 0; i < 6; i++) {
+ addEdge(graph, edges[i][0], edges[i][1]);
+ }
+ printf("\n초기화한 그래프:\n");
+ printGraphAdjMat(graph);
+
+ /* 간선 추가 */
+ // 정점 1, 2의 인덱스는 각각 0, 2이다
+ addEdge(graph, 0, 2);
+ printf("\n간선 1-2를 추가한 후의 그래프:\n");
+ printGraphAdjMat(graph);
+
+ /* 간선 삭제 */
+ // 정점 1, 3의 인덱스는 각각 0, 1이다
+ removeEdge(graph, 0, 1);
+ printf("\n간선 1-3을 삭제한 후의 그래프:\n");
+ printGraphAdjMat(graph);
+
+ /* 정점 추가 */
+ addVertex(graph, 6);
+ printf("\n정점 6을 추가한 후의 그래프:\n");
+ printGraphAdjMat(graph);
+
+ /* 정점 삭제 */
+ // 정점 3의 인덱스는 1이다
+ removeVertex(graph, 1);
+ printf("\n정점 3을 삭제한 후의 그래프:\n");
+ printGraphAdjMat(graph);
+
+ // 메모리 해제
+ delGraphAdjMat(graph);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_graph/graph_bfs.c b/ko/codes/c/chapter_graph/graph_bfs.c
new file mode 100644
index 000000000..7a0687410
--- /dev/null
+++ b/ko/codes/c/chapter_graph/graph_bfs.c
@@ -0,0 +1,116 @@
+/**
+ * File: graph_bfs.c
+ * Created Time: 2023-07-11
+ * Author: NI-SW (947743645@qq.com)
+ */
+
+#include "graph_adjacency_list.c"
+
+// 노드가 최대 100개라고 가정한다
+#define MAX_SIZE 100
+
+/* 노드 큐 구조체 */
+typedef struct {
+ Vertex *vertices[MAX_SIZE];
+ int front, rear, size;
+} Queue;
+
+/* 생성자 */
+Queue *newQueue() {
+ Queue *q = (Queue *)malloc(sizeof(Queue));
+ q->front = q->rear = q->size = 0;
+ return q;
+}
+
+/* 큐가 비어 있는지 판단 */
+int isEmpty(Queue *q) {
+ return q->size == 0;
+}
+
+/* 큐 삽입 연산 */
+void enqueue(Queue *q, Vertex *vet) {
+ q->vertices[q->rear] = vet;
+ q->rear = (q->rear + 1) % MAX_SIZE;
+ q->size++;
+}
+
+/* 큐에서 꺼내는 연산 */
+Vertex *dequeue(Queue *q) {
+ Vertex *vet = q->vertices[q->front];
+ q->front = (q->front + 1) % MAX_SIZE;
+ q->size--;
+ return vet;
+}
+
+/* 정점을 이미 방문했는지 확인 */
+int isVisited(Vertex **visited, int size, Vertex *vet) {
+ // O(n) 시간에 순회하여 노드를 찾는다
+ for (int i = 0; i < size; i++) {
+ if (visited[i] == vet)
+ return 1;
+ }
+ return 0;
+}
+
+/* 너비 우선 순회 */
+// 지정한 정점의 모든 인접 정점을 얻기 쉽도록 그래프를 인접 리스트로 표현한다
+void graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) {
+ // 큐를 사용하여 BFS를 구현한다
+ Queue *queue = newQueue();
+ enqueue(queue, startVet);
+ visited[(*visitedSize)++] = startVet;
+ // 정점 vet에서 시작하여 모든 정점을 방문할 때까지 반복한다
+ while (!isEmpty(queue)) {
+ Vertex *vet = dequeue(queue); // 큐 맨 앞의 정점을 꺼낸다
+ res[(*resSize)++] = vet; // 방문한 정점을 기록한다
+ // 해당 정점의 모든 인접 정점을 순회한다
+ AdjListNode *node = findNode(graph, vet);
+ while (node != NULL) {
+ // 이미 방문한 정점은 건너뛴다
+ if (!isVisited(visited, *visitedSize, node->vertex)) {
+ enqueue(queue, node->vertex); // 방문하지 않은 정점만 큐에 삽입한다
+ visited[(*visitedSize)++] = node->vertex; // 해당 정점을 방문했다고 표시한다
+ }
+ node = node->next;
+ }
+ }
+ // 메모리 해제
+ free(queue);
+}
+
+/* 실행 코드 */
+int main() {
+ // 무방향 그래프 초기화
+ int vals[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ int size = sizeof(vals) / sizeof(vals[0]);
+ Vertex **v = valsToVets(vals, size);
+ Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[1], v[4]}, {v[2], v[5]}, {v[3], v[4]},
+ {v[3], v[6]}, {v[4], v[5]}, {v[4], v[7]}, {v[5], v[8]}, {v[6], v[7]}, {v[7], v[8]}};
+ int egdeSize = sizeof(edges) / sizeof(edges[0]);
+ GraphAdjList *graph = newGraphAdjList();
+ // 모든 정점과 간선을 추가한다
+ for (int i = 0; i < size; i++) {
+ addVertex(graph, v[i]);
+ }
+ for (int i = 0; i < egdeSize; i++) {
+ addEdge(graph, edges[i][0], edges[i][1]);
+ }
+ printf("\n초기화한 그래프:\n");
+ printGraph(graph);
+
+ // 너비 우선 순회
+ // 정점 순회 순서
+ Vertex *res[MAX_SIZE];
+ int resSize = 0;
+ // 이미 방문한 정점을 기록한다
+ Vertex *visited[MAX_SIZE];
+ int visitedSize = 0;
+ graphBFS(graph, v[0], res, &resSize, visited, &visitedSize);
+ printf("\n너비 우선 순회(BFS)의 정점 순서:\n");
+ printArray(vetsToVals(res, resSize), resSize);
+
+ // 메모리 해제
+ delGraphAdjList(graph);
+ free(v);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_graph/graph_dfs.c b/ko/codes/c/chapter_graph/graph_dfs.c
new file mode 100644
index 000000000..59857c7c6
--- /dev/null
+++ b/ko/codes/c/chapter_graph/graph_dfs.c
@@ -0,0 +1,75 @@
+/**
+ * File: graph_dfs.c
+ * Created Time: 2023-07-13
+ * Author: NI-SW (947743645@qq.com)
+ */
+
+#include "graph_adjacency_list.c"
+
+// 노드가 최대 100개라고 가정한다
+#define MAX_SIZE 100
+
+/* 정점을 이미 방문했는지 확인 */
+int isVisited(Vertex **res, int size, Vertex *vet) {
+ // O(n) 시간에 순회하여 노드를 찾는다
+ for (int i = 0; i < size; i++) {
+ if (res[i] == vet) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* 깊이 우선 순회 보조 함수 */
+void dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) {
+ // 방문한 정점을 기록한다
+ res[(*resSize)++] = vet;
+ // 해당 정점의 모든 인접 정점을 순회한다
+ AdjListNode *node = findNode(graph, vet);
+ while (node != NULL) {
+ // 이미 방문한 정점은 건너뛴다
+ if (!isVisited(res, *resSize, node->vertex)) {
+ // 인접 정점을 재귀적으로 방문한다
+ dfs(graph, res, resSize, node->vertex);
+ }
+ node = node->next;
+ }
+}
+
+/* 깊이 우선 순회 */
+// 지정한 정점의 모든 인접 정점을 얻기 쉽도록 그래프를 인접 리스트로 표현한다
+void graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) {
+ dfs(graph, res, resSize, startVet);
+}
+
+/* 실행 코드 */
+int main() {
+ // 무방향 그래프 초기화
+ int vals[] = {0, 1, 2, 3, 4, 5, 6};
+ int size = sizeof(vals) / sizeof(vals[0]);
+ Vertex **v = valsToVets(vals, size);
+ Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}};
+ int egdeSize = sizeof(edges) / sizeof(edges[0]);
+ GraphAdjList *graph = newGraphAdjList();
+ // 모든 정점과 간선을 추가한다
+ for (int i = 0; i < size; i++) {
+ addVertex(graph, v[i]);
+ }
+ for (int i = 0; i < egdeSize; i++) {
+ addEdge(graph, edges[i][0], edges[i][1]);
+ }
+ printf("\n초기화한 그래프:\n");
+ printGraph(graph);
+
+ // 깊이 우선 순회
+ Vertex *res[MAX_SIZE];
+ int resSize = 0;
+ graphDFS(graph, v[0], res, &resSize);
+ printf("\n깊이 우선 순회(DFS)의 정점 순서:\n");
+ printArray(vetsToVals(res, resSize), resSize);
+
+ // 메모리 해제
+ delGraphAdjList(graph);
+ free(v);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_greedy/CMakeLists.txt b/ko/codes/c/chapter_greedy/CMakeLists.txt
new file mode 100644
index 000000000..b8e6ca425
--- /dev/null
+++ b/ko/codes/c/chapter_greedy/CMakeLists.txt
@@ -0,0 +1,8 @@
+add_executable(coin_change_greedy coin_change_greedy.c)
+add_executable(fractional_knapsack fractional_knapsack.c)
+add_executable(max_capacity max_capacity.c)
+add_executable(max_product_cutting max_product_cutting.c)
+
+if (NOT CMAKE_C_COMPILER_ID STREQUAL "MSVC")
+ target_link_libraries(max_product_cutting m)
+endif()
diff --git a/ko/codes/c/chapter_greedy/coin_change_greedy.c b/ko/codes/c/chapter_greedy/coin_change_greedy.c
new file mode 100644
index 000000000..de10c6476
--- /dev/null
+++ b/ko/codes/c/chapter_greedy/coin_change_greedy.c
@@ -0,0 +1,60 @@
+/**
+ * File: coin_change_greedy.c
+ * Created Time: 2023-09-07
+ * Author: lwbaptx (lwbaptx@gmail.com)
+ */
+
+#include "../utils/common.h"
+
+/* 동전 교환: 탐욕 */
+int coinChangeGreedy(int *coins, int size, int amt) {
+ // coins 리스트가 정렬되어 있다고 가정한다
+ int i = size - 1;
+ int count = 0;
+ // 남은 금액이 없을 때까지 탐욕적 선택을 반복한다
+ while (amt > 0) {
+ // 남은 금액보다 작으면서 가장 가까운 금액의 동전을 찾는다
+ while (i > 0 && coins[i] > amt) {
+ i--;
+ }
+ // coins[i]를 선택한다
+ amt -= coins[i];
+ count++;
+ }
+ // 가능한 방법을 찾지 못하면 -1을 반환한다
+ return amt == 0 ? count : -1;
+}
+
+/* 실행 코드 */
+int main() {
+ // 탐욕: 전역 최적해를 찾는 것이 보장된다
+ int coins1[6] = {1, 5, 10, 20, 50, 100};
+ int amt = 186;
+ int res = coinChangeGreedy(coins1, 6, amt);
+ printf("\ncoins = ");
+ printArray(coins1, 6);
+ printf("amt = %d\n", amt);
+ printf("%d의 금액을 만드는 데 필요한 최소 동전 개수는 %d이다\n", amt, res);
+
+ // 탐욕: 전역 최적해를 찾는 것이 보장되지 않는다
+ int coins2[3] = {1, 20, 50};
+ amt = 60;
+ res = coinChangeGreedy(coins2, 3, amt);
+ printf("\ncoins = ");
+ printArray(coins2, 3);
+ printf("amt = %d\n", amt);
+ printf("%d의 금액을 만드는 데 필요한 최소 동전 개수는 %d이다\n", amt, res);
+ printf("실제 최소 개수는 3이며, 20 + 20 + 20으로 만들 수 있다\n");
+
+ // 탐욕: 전역 최적해를 찾는 것이 보장되지 않는다
+ int coins3[3] = {1, 49, 50};
+ amt = 98;
+ res = coinChangeGreedy(coins3, 3, amt);
+ printf("\ncoins = ");
+ printArray(coins3, 3);
+ printf("amt = %d\n", amt);
+ printf("%d의 금액을 만드는 데 필요한 최소 동전 개수는 %d이다\n", amt, res);
+ printf("실제 최소 개수는 2이며, 49 + 49로 만들 수 있다\n");
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_greedy/fractional_knapsack.c b/ko/codes/c/chapter_greedy/fractional_knapsack.c
new file mode 100644
index 000000000..bb1c38224
--- /dev/null
+++ b/ko/codes/c/chapter_greedy/fractional_knapsack.c
@@ -0,0 +1,60 @@
+/**
+ * File: fractional_knapsack.c
+ * Created Time: 2023-09-14
+ * Author: xianii (xianyi.xia@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 물건 */
+typedef struct {
+ int w; // 물건의 무게
+ int v; // 물건의 가치
+} Item;
+
+/* 가치 밀도에 따라 정렬 */
+int sortByValueDensity(const void *a, const void *b) {
+ Item *t1 = (Item *)a;
+ Item *t2 = (Item *)b;
+ return (float)(t1->v) / t1->w < (float)(t2->v) / t2->w;
+}
+
+/* 분할 가능한 배낭: 탐욕 */
+float fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) {
+ // 무게와 가치 두 속성을 갖는 물건의 리스트를 생성한다
+ Item *items = malloc(sizeof(Item) * itemCount);
+ for (int i = 0; i < itemCount; i++) {
+ items[i] = (Item){.w = wgt[i], .v = val[i]};
+ }
+ // 단위 가치 item.v / item.w를 기준으로 내림차순 정렬한다
+ qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity);
+ // 탐욕적 선택을 반복한다
+ float res = 0.0;
+ for (int i = 0; i < itemCount; i++) {
+ if (items[i].w <= cap) {
+ // 남은 용량이 충분하면 현재 물건을 통째로 배낭에 넣는다
+ res += items[i].v;
+ cap -= items[i].w;
+ } else {
+ // 남은 용량이 부족하면 현재 물건의 일부를 배낭에 넣는다
+ res += (float)cap / items[i].w * items[i].v;
+ cap = 0;
+ break;
+ }
+ }
+ free(items);
+ return res;
+}
+
+/* 실행 코드 */
+int main(void) {
+ int wgt[] = {10, 20, 30, 40, 50};
+ int val[] = {50, 120, 150, 210, 240};
+ int capacity = 50;
+
+ // 탐욕 알고리즘
+ float res = fractionalKnapsack(wgt, val, sizeof(wgt) / sizeof(int), capacity);
+ printf("배낭 용량을 넘지 않는 물건의 최대 가치는 %0.2f이다\n", res);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_greedy/max_capacity.c b/ko/codes/c/chapter_greedy/max_capacity.c
new file mode 100644
index 000000000..c374594f6
--- /dev/null
+++ b/ko/codes/c/chapter_greedy/max_capacity.c
@@ -0,0 +1,49 @@
+/**
+ * File: max_capacity.c
+ * Created Time: 2023-09-15
+ * Author: xianii (xianyi.xia@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 최솟값 구하기 */
+int myMin(int a, int b) {
+ return a < b ? a : b;
+}
+/* 최댓값 구하기 */
+int myMax(int a, int b) {
+ return a > b ? a : b;
+}
+
+/* 최대 용량: 탐욕 */
+int maxCapacity(int ht[], int htLength) {
+ // i와 j가 각각 배열의 양 끝을 가리키도록 초기화한다
+ int i = 0;
+ int j = htLength - 1;
+ // 최대 용량의 초깃값은 0이다
+ int res = 0;
+ // 두 칸막이가 만날 때까지 탐욕적 선택을 반복한다
+ while (i < j) {
+ // 최대 용량을 갱신한다
+ int capacity = myMin(ht[i], ht[j]) * (j - i);
+ res = myMax(res, capacity);
+ // 낮은 칸막이를 안쪽으로 이동한다
+ if (ht[i] < ht[j]) {
+ i++;
+ } else {
+ j--;
+ }
+ }
+ return res;
+}
+
+/* 실행 코드 */
+int main(void) {
+ int ht[] = {3, 8, 5, 2, 7, 7, 3, 4};
+
+ // 탐욕 알고리즘
+ int res = maxCapacity(ht, sizeof(ht) / sizeof(int));
+ printf("최대 용량은 %d이다\n", res);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_greedy/max_product_cutting.c b/ko/codes/c/chapter_greedy/max_product_cutting.c
new file mode 100644
index 000000000..75f171266
--- /dev/null
+++ b/ko/codes/c/chapter_greedy/max_product_cutting.c
@@ -0,0 +1,38 @@
+/**
+ * File: max_product_cutting.c
+ * Created Time: 2023-09-15
+ * Author: xianii (xianyi.xia@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 분할 후의 최대 곱: 탐욕 */
+int maxProductCutting(int n) {
+ // n <= 3이면 반드시 1 하나가 나오도록 분할해야 한다
+ if (n <= 3) {
+ return 1 * (n - 1);
+ }
+ // 탐욕적으로 3을 떼어 낸다. a는 3의 개수이고 b는 나머지이다
+ int a = n / 3;
+ int b = n % 3;
+ if (b == 1) {
+ // 나머지가 1이면 1 * 3 한 쌍을 2 * 2로 바꾼다
+ return pow(3, a - 1) * 2 * 2;
+ }
+ if (b == 2) {
+ // 나머지가 2이면 별도로 처리하지 않는다
+ return pow(3, a) * 2;
+ }
+ // 나머지가 0이면 별도로 처리하지 않는다
+ return pow(3, a);
+}
+
+/* 실행 코드 */
+int main(void) {
+ int n = 58;
+ // 탐욕 알고리즘
+ int res = maxProductCutting(n);
+ printf("분할한 수의 최대 곱은 %d이다\n", res);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_hashing/CMakeLists.txt b/ko/codes/c/chapter_hashing/CMakeLists.txt
new file mode 100644
index 000000000..9ac951ae4
--- /dev/null
+++ b/ko/codes/c/chapter_hashing/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_executable(array_hash_map array_hash_map.c)
+add_executable(hash_map_chaining hash_map_chaining.c)
+add_executable(hash_map_open_addressing hash_map_open_addressing.c)
+add_executable(simple_hash simple_hash.c)
diff --git a/ko/codes/c/chapter_hashing/array_hash_map.c b/ko/codes/c/chapter_hashing/array_hash_map.c
new file mode 100644
index 000000000..fb478f28e
--- /dev/null
+++ b/ko/codes/c/chapter_hashing/array_hash_map.c
@@ -0,0 +1,215 @@
+/**
+ * File: array_hash_map.c
+ * Created Time: 2023-03-18
+ * Author: Guanngxu (446678850@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 해시 테이블의 기본 크기 */
+#define MAX_SIZE 100
+
+/* 키-값 쌍 int->string */
+typedef struct {
+ int key;
+ char *val;
+} Pair;
+
+/* 키-값 쌍의 집합 */
+typedef struct {
+ void *set;
+ int len;
+} MapSet;
+
+/* 배열로 구현한 해시 테이블 */
+typedef struct {
+ Pair *buckets[MAX_SIZE];
+} ArrayHashMap;
+
+/* 생성자 */
+ArrayHashMap *newArrayHashMap() {
+ ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap));
+ for (int i=0; i < MAX_SIZE; i++) {
+ hmap->buckets[i] = NULL;
+ }
+ return hmap;
+}
+
+/* 소멸자 */
+void delArrayHashMap(ArrayHashMap *hmap) {
+ for (int i = 0; i < MAX_SIZE; i++) {
+ if (hmap->buckets[i] != NULL) {
+ free(hmap->buckets[i]->val);
+ free(hmap->buckets[i]);
+ }
+ }
+ free(hmap);
+}
+
+/* 해시 함수 */
+int hashFunc(int key) {
+ int index = key % MAX_SIZE;
+ return index;
+}
+
+/* 조회 연산 */
+const char *get(const ArrayHashMap *hmap, const int key) {
+ int index = hashFunc(key);
+ const Pair *Pair = hmap->buckets[index];
+ if (Pair == NULL)
+ return NULL;
+ return Pair->val;
+}
+
+/* 추가 연산 */
+void put(ArrayHashMap *hmap, const int key, const char *val) {
+ Pair *Pair = malloc(sizeof(Pair));
+ Pair->key = key;
+ Pair->val = malloc(strlen(val) + 1);
+ strcpy(Pair->val, val);
+
+ int index = hashFunc(key);
+ hmap->buckets[index] = Pair;
+}
+
+/* 삭제 연산 */
+void removeItem(ArrayHashMap *hmap, const int key) {
+ int index = hashFunc(key);
+ free(hmap->buckets[index]->val);
+ free(hmap->buckets[index]);
+ hmap->buckets[index] = NULL;
+}
+
+/* 모든 키-값 쌍 반환 */
+void pairSet(ArrayHashMap *hmap, MapSet *set) {
+ Pair *entries;
+ int i = 0, index = 0;
+ int total = 0;
+ /* 유효한 키-값 쌍의 개수를 센다 */
+ for (i = 0; i < MAX_SIZE; i++) {
+ if (hmap->buckets[i] != NULL) {
+ total++;
+ }
+ }
+ entries = malloc(sizeof(Pair) * total);
+ for (i = 0; i < MAX_SIZE; i++) {
+ if (hmap->buckets[i] != NULL) {
+ entries[index].key = hmap->buckets[i]->key;
+ entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1);
+ strcpy(entries[index].val, hmap->buckets[i]->val);
+ index++;
+ }
+ }
+ set->set = entries;
+ set->len = total;
+}
+
+/* 모든 키 반환 */
+void keySet(ArrayHashMap *hmap, MapSet *set) {
+ int *keys;
+ int i = 0, index = 0;
+ int total = 0;
+ /* 유효한 키-값 쌍의 개수를 센다 */
+ for (i = 0; i < MAX_SIZE; i++) {
+ if (hmap->buckets[i] != NULL) {
+ total++;
+ }
+ }
+ keys = malloc(total * sizeof(int));
+ for (i = 0; i < MAX_SIZE; i++) {
+ if (hmap->buckets[i] != NULL) {
+ keys[index] = hmap->buckets[i]->key;
+ index++;
+ }
+ }
+ set->set = keys;
+ set->len = total;
+}
+
+/* 모든 값 반환 */
+void valueSet(ArrayHashMap *hmap, MapSet *set) {
+ char **vals;
+ int i = 0, index = 0;
+ int total = 0;
+ /* 유효한 키-값 쌍의 개수를 센다 */
+ for (i = 0; i < MAX_SIZE; i++) {
+ if (hmap->buckets[i] != NULL) {
+ total++;
+ }
+ }
+ vals = malloc(total * sizeof(char *));
+ for (i = 0; i < MAX_SIZE; i++) {
+ if (hmap->buckets[i] != NULL) {
+ vals[index] = hmap->buckets[i]->val;
+ index++;
+ }
+ }
+ set->set = vals;
+ set->len = total;
+}
+
+/* 해시 테이블 출력 */
+void print(ArrayHashMap *hmap) {
+ int i;
+ MapSet set;
+ pairSet(hmap, &set);
+ Pair *entries = (Pair *)set.set;
+ for (i = 0; i < set.len; i++) {
+ printf("%d -> %s\n", entries[i].key, entries[i].val);
+ }
+ free(set.set);
+}
+
+/* 실행 코드 */
+int main() {
+ /* 해시 테이블 초기화 */
+ ArrayHashMap *hmap = newArrayHashMap();
+
+ /* 추가 연산 */
+ // 해시 테이블에 (key, value) 키-값 쌍을 추가한다
+ put(hmap, 12836, "小哈");
+ put(hmap, 15937, "小啰");
+ put(hmap, 16750, "小算");
+ put(hmap, 13276, "小法");
+ put(hmap, 10583, "小鸭");
+ printf("\n추가한 후의 해시 테이블:\n키 -> 값\n");
+ print(hmap);
+
+ /* 조회 연산 */
+ // 해시 테이블에 키 key를 입력하여 값 value를 얻는다
+ const char *name = get(hmap, 15937);
+ printf("\n학번 15937을 입력하여 조회한 이름: %s\n", name);
+
+ /* 삭제 연산 */
+ // 해시 테이블에서 (key, value) 키-값 쌍을 삭제한다
+ removeItem(hmap, 10583);
+ printf("\n10583을 삭제한 후의 해시 테이블:\n키 -> 값\n");
+ print(hmap);
+
+ /* 해시 테이블 순회 */
+ int i;
+
+ printf("\n키-값 쌍(키->값) 순회\n");
+ print(hmap);
+
+ MapSet set;
+
+ keySet(hmap, &set);
+ int *keys = (int *)set.set;
+ printf("\n키 Key만 순회\n");
+ for (i = 0; i < set.len; i++) {
+ printf("%d\n", keys[i]);
+ }
+ free(set.set);
+
+ valueSet(hmap, &set);
+ char **vals = (char **)set.set;
+ printf("\n키 Value만 순회\n");
+ for (i = 0; i < set.len; i++) {
+ printf("%s\n", vals[i]);
+ }
+ free(set.set);
+
+ delArrayHashMap(hmap);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_hashing/hash_map_chaining.c b/ko/codes/c/chapter_hashing/hash_map_chaining.c
new file mode 100644
index 000000000..cf29ef4ce
--- /dev/null
+++ b/ko/codes/c/chapter_hashing/hash_map_chaining.c
@@ -0,0 +1,213 @@
+/**
+ * File: hash_map_chaining.c
+ * Created Time: 2023-10-13
+ * Author: SenMing (1206575349@qq.com), krahets (krahets@163.com)
+ */
+
+#include
+#include
+#include
+
+// val의 최대 길이가 100이라고 가정한다
+#define MAX_SIZE 100
+
+/* 키-값 쌍 */
+typedef struct {
+ int key;
+ char val[MAX_SIZE];
+} Pair;
+
+/* 연결 리스트 노드 */
+typedef struct Node {
+ Pair *pair;
+ struct Node *next;
+} Node;
+
+/* 체이닝 해시 테이블 */
+typedef struct {
+ int size; // 키-값 쌍 개수
+ int capacity; // 해시 테이블 용량
+ double loadThres; // 용량 확장을 수행하는 적재율 임계값
+ int extendRatio; // 용량 확장 배수
+ Node **buckets; // 버킷 배열
+} HashMapChaining;
+
+/* 생성자 */
+HashMapChaining *newHashMapChaining() {
+ HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining));
+ hashMap->size = 0;
+ hashMap->capacity = 4;
+ hashMap->loadThres = 2.0 / 3.0;
+ hashMap->extendRatio = 2;
+ hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *));
+ for (int i = 0; i < hashMap->capacity; i++) {
+ hashMap->buckets[i] = NULL;
+ }
+ return hashMap;
+}
+
+/* 소멸자 */
+void delHashMapChaining(HashMapChaining *hashMap) {
+ for (int i = 0; i < hashMap->capacity; i++) {
+ Node *cur = hashMap->buckets[i];
+ while (cur) {
+ Node *tmp = cur;
+ cur = cur->next;
+ free(tmp->pair);
+ free(tmp);
+ }
+ }
+ free(hashMap->buckets);
+ free(hashMap);
+}
+
+/* 해시 함수 */
+int hashFunc(HashMapChaining *hashMap, int key) {
+ return key % hashMap->capacity;
+}
+
+/* 적재율 */
+double loadFactor(HashMapChaining *hashMap) {
+ return (double)hashMap->size / (double)hashMap->capacity;
+}
+
+/* 조회 연산 */
+char *get(HashMapChaining *hashMap, int key) {
+ int index = hashFunc(hashMap, key);
+ // 버킷을 순회하다 key를 찾으면 해당 val을 반환한다
+ Node *cur = hashMap->buckets[index];
+ while (cur) {
+ if (cur->pair->key == key) {
+ return cur->pair->val;
+ }
+ cur = cur->next;
+ }
+ return ""; // key를 찾지 못하면 빈 문자열을 반환한다
+}
+
+/* 추가 연산 */
+void put(HashMapChaining *hashMap, int key, const char *val);
+
+/* 해시 테이블 용량 확장 */
+void extend(HashMapChaining *hashMap) {
+ // 원래 해시 테이블을 임시로 저장한다
+ int oldCapacity = hashMap->capacity;
+ Node **oldBuckets = hashMap->buckets;
+ // 용량을 확장한 새 해시 테이블을 초기화한다
+ hashMap->capacity *= hashMap->extendRatio;
+ hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *));
+ for (int i = 0; i < hashMap->capacity; i++) {
+ hashMap->buckets[i] = NULL;
+ }
+ hashMap->size = 0;
+ // 원래 해시 테이블의 키-값 쌍을 새 해시 테이블로 옮긴다
+ for (int i = 0; i < oldCapacity; i++) {
+ Node *cur = oldBuckets[i];
+ while (cur) {
+ put(hashMap, cur->pair->key, cur->pair->val);
+ Node *temp = cur;
+ cur = cur->next;
+ // 메모리 해제
+ free(temp->pair);
+ free(temp);
+ }
+ }
+
+ free(oldBuckets);
+}
+
+/* 추가 연산 */
+void put(HashMapChaining *hashMap, int key, const char *val) {
+ // 적재율이 임계값을 초과하면 용량을 확장한다
+ if (loadFactor(hashMap) > hashMap->loadThres) {
+ extend(hashMap);
+ }
+ int index = hashFunc(hashMap, key);
+ // 버킷을 순회하다 지정한 key를 만나면 해당 val을 갱신하고 반환한다
+ Node *cur = hashMap->buckets[index];
+ while (cur) {
+ if (cur->pair->key == key) {
+ strcpy(cur->pair->val, val); // 지정한 key를 만나면 해당 val을 갱신하고 반환한다
+ return;
+ }
+ cur = cur->next;
+ }
+ // 해당 key가 없으면 키-값 쌍을 연결 리스트의 맨 앞에 추가한다
+ Pair *newPair = (Pair *)malloc(sizeof(Pair));
+ newPair->key = key;
+ strcpy(newPair->val, val);
+ Node *newNode = (Node *)malloc(sizeof(Node));
+ newNode->pair = newPair;
+ newNode->next = hashMap->buckets[index];
+ hashMap->buckets[index] = newNode;
+ hashMap->size++;
+}
+
+/* 삭제 연산 */
+void removeItem(HashMapChaining *hashMap, int key) {
+ int index = hashFunc(hashMap, key);
+ Node *cur = hashMap->buckets[index];
+ Node *pre = NULL;
+ while (cur) {
+ if (cur->pair->key == key) {
+ // 해당 키-값 쌍을 삭제한다
+ if (pre) {
+ pre->next = cur->next;
+ } else {
+ hashMap->buckets[index] = cur->next;
+ }
+ // 메모리 해제
+ free(cur->pair);
+ free(cur);
+ hashMap->size--;
+ return;
+ }
+ pre = cur;
+ cur = cur->next;
+ }
+}
+
+/* 해시 테이블 출력 */
+void print(HashMapChaining *hashMap) {
+ for (int i = 0; i < hashMap->capacity; i++) {
+ Node *cur = hashMap->buckets[i];
+ printf("[");
+ while (cur) {
+ printf("%d -> %s, ", cur->pair->key, cur->pair->val);
+ cur = cur->next;
+ }
+ printf("]\n");
+ }
+}
+
+/* 실행 코드 */
+int main() {
+ /* 해시 테이블 초기화 */
+ HashMapChaining *hashMap = newHashMapChaining();
+
+ /* 추가 연산 */
+ // 해시 테이블에 (key, value) 키-값 쌍을 추가한다
+ put(hashMap, 12836, "小哈");
+ put(hashMap, 15937, "小啰");
+ put(hashMap, 16750, "小算");
+ put(hashMap, 13276, "小法");
+ put(hashMap, 10583, "小鸭");
+ printf("\n추가한 후의 해시 테이블:\n키 -> 값\n");
+ print(hashMap);
+
+ /* 조회 연산 */
+ // 해시 테이블에 키 key를 입력하여 값 value를 얻는다
+ char *name = get(hashMap, 13276);
+ printf("\n학번 13276을 입력하여 조회한 이름: %s\n", name);
+
+ /* 삭제 연산 */
+ // 해시 테이블에서 (key, value) 키-값 쌍을 삭제한다
+ removeItem(hashMap, 12836);
+ printf("\n학번 12836을 삭제한 후의 해시 테이블\n키 -> 값\n");
+ print(hashMap);
+
+ /* 해시 테이블의 메모리 해제 */
+ delHashMapChaining(hashMap);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_hashing/hash_map_open_addressing.c b/ko/codes/c/chapter_hashing/hash_map_open_addressing.c
new file mode 100644
index 000000000..e6f3acd3c
--- /dev/null
+++ b/ko/codes/c/chapter_hashing/hash_map_open_addressing.c
@@ -0,0 +1,211 @@
+/**
+ * File: hash_map_open_addressing.c
+ * Created Time: 2023-10-6
+ * Author: lclc6 (w1929522410@163.com)
+ */
+
+#include "../utils/common.h"
+
+/* 개방 주소법 해시 테이블 */
+typedef struct {
+ int key;
+ char *val;
+} Pair;
+
+/* 개방 주소법 해시 테이블 */
+typedef struct {
+ int size; // 키-값 쌍 개수
+ int capacity; // 해시 테이블 용량
+ double loadThres; // 용량 확장을 수행하는 적재율 임계값
+ int extendRatio; // 용량 확장 배수
+ Pair **buckets; // 버킷 배열
+ Pair *TOMBSTONE; // 삭제 표시
+} HashMapOpenAddressing;
+
+// 함수 선언
+void extend(HashMapOpenAddressing *hashMap);
+
+/* 생성자 */
+HashMapOpenAddressing *newHashMapOpenAddressing() {
+ HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing));
+ hashMap->size = 0;
+ hashMap->capacity = 4;
+ hashMap->loadThres = 2.0 / 3.0;
+ hashMap->extendRatio = 2;
+ hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *));
+ hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair));
+ hashMap->TOMBSTONE->key = -1;
+ hashMap->TOMBSTONE->val = "-1";
+
+ return hashMap;
+}
+
+/* 소멸자 */
+void delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) {
+ for (int i = 0; i < hashMap->capacity; i++) {
+ Pair *pair = hashMap->buckets[i];
+ if (pair != NULL && pair != hashMap->TOMBSTONE) {
+ free(pair->val);
+ free(pair);
+ }
+ }
+ free(hashMap->buckets);
+ free(hashMap->TOMBSTONE);
+ free(hashMap);
+}
+
+/* 해시 함수 */
+int hashFunc(HashMapOpenAddressing *hashMap, int key) {
+ return key % hashMap->capacity;
+}
+
+/* 적재율 */
+double loadFactor(HashMapOpenAddressing *hashMap) {
+ return (double)hashMap->size / (double)hashMap->capacity;
+}
+
+/* key에 해당하는 버킷 인덱스 탐색 */
+int findBucket(HashMapOpenAddressing *hashMap, int key) {
+ int index = hashFunc(hashMap, key);
+ int firstTombstone = -1;
+ // 선형 탐사로 탐색하고, 빈 버킷을 만나면 반복문을 종료한다
+ while (hashMap->buckets[index] != NULL) {
+ // key를 만나면 해당 버킷 인덱스를 반환한다
+ if (hashMap->buckets[index]->key == key) {
+ // 이전에 삭제 표시를 만났으면 키-값 쌍을 그 인덱스로 옮긴다
+ if (firstTombstone != -1) {
+ hashMap->buckets[firstTombstone] = hashMap->buckets[index];
+ hashMap->buckets[index] = hashMap->TOMBSTONE;
+ return firstTombstone; // 옮긴 후의 버킷 인덱스를 반환한다
+ }
+ return index; // 버킷 인덱스를 반환한다
+ }
+ // 처음 만난 삭제 표시를 기록한다
+ if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) {
+ firstTombstone = index;
+ }
+ // 버킷 인덱스를 계산하고, 끝을 넘어가면 처음으로 돌아온다
+ index = (index + 1) % hashMap->capacity;
+ }
+ // key가 없으면 추가할 위치의 인덱스를 반환한다
+ return firstTombstone == -1 ? index : firstTombstone;
+}
+
+/* 조회 연산 */
+char *get(HashMapOpenAddressing *hashMap, int key) {
+ // key에 해당하는 버킷 인덱스 탐색
+ int index = findBucket(hashMap, key);
+ // 키-값 쌍을 찾으면 해당 val을 반환한다
+ if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) {
+ return hashMap->buckets[index]->val;
+ }
+ // 키-값 쌍이 없으면 빈 문자열을 반환한다
+ return "";
+}
+
+/* 추가 연산 */
+void put(HashMapOpenAddressing *hashMap, int key, char *val) {
+ // 적재율이 임계값을 초과하면 용량을 확장한다
+ if (loadFactor(hashMap) > hashMap->loadThres) {
+ extend(hashMap);
+ }
+ // key에 해당하는 버킷 인덱스 탐색
+ int index = findBucket(hashMap, key);
+ // 키-값 쌍을 찾으면 val을 덮어쓰고 반환한다
+ if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) {
+ free(hashMap->buckets[index]->val);
+ hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1));
+ strcpy(hashMap->buckets[index]->val, val);
+ hashMap->buckets[index]->val[strlen(val)] = '\0';
+ return;
+ }
+ // 키-값 쌍이 없으면 해당 키-값 쌍을 추가한다
+ Pair *pair = (Pair *)malloc(sizeof(Pair));
+ pair->key = key;
+ pair->val = (char *)malloc(sizeof(strlen(val) + 1));
+ strcpy(pair->val, val);
+ pair->val[strlen(val)] = '\0';
+
+ hashMap->buckets[index] = pair;
+ hashMap->size++;
+}
+
+/* 삭제 연산 */
+void removeItem(HashMapOpenAddressing *hashMap, int key) {
+ // key에 해당하는 버킷 인덱스 탐색
+ int index = findBucket(hashMap, key);
+ // 키-값 쌍을 찾으면 삭제 표시로 덮어쓴다
+ if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) {
+ Pair *pair = hashMap->buckets[index];
+ free(pair->val);
+ free(pair);
+ hashMap->buckets[index] = hashMap->TOMBSTONE;
+ hashMap->size--;
+ }
+}
+
+/* 해시 테이블 용량 확장 */
+void extend(HashMapOpenAddressing *hashMap) {
+ // 원래 해시 테이블을 임시로 저장한다
+ Pair **bucketsTmp = hashMap->buckets;
+ int oldCapacity = hashMap->capacity;
+ // 용량을 확장한 새 해시 테이블을 초기화한다
+ hashMap->capacity *= hashMap->extendRatio;
+ hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *));
+ hashMap->size = 0;
+ // 원래 해시 테이블의 키-값 쌍을 새 해시 테이블로 옮긴다
+ for (int i = 0; i < oldCapacity; i++) {
+ Pair *pair = bucketsTmp[i];
+ if (pair != NULL && pair != hashMap->TOMBSTONE) {
+ put(hashMap, pair->key, pair->val);
+ free(pair->val);
+ free(pair);
+ }
+ }
+ free(bucketsTmp);
+}
+
+/* 해시 테이블 출력 */
+void print(HashMapOpenAddressing *hashMap) {
+ for (int i = 0; i < hashMap->capacity; i++) {
+ Pair *pair = hashMap->buckets[i];
+ if (pair == NULL) {
+ printf("NULL\n");
+ } else if (pair == hashMap->TOMBSTONE) {
+ printf("TOMBSTONE\n");
+ } else {
+ printf("%d -> %s\n", pair->key, pair->val);
+ }
+ }
+}
+
+/* 실행 코드 */
+int main() {
+ // 해시 테이블 초기화
+ HashMapOpenAddressing *hashmap = newHashMapOpenAddressing();
+
+ // 추가 연산
+ // 해시 테이블에 키-값 쌍 (key, val)을 추가한다
+ put(hashmap, 12836, "小哈");
+ put(hashmap, 15937, "小啰");
+ put(hashmap, 16750, "小算");
+ put(hashmap, 13276, "小法");
+ put(hashmap, 10583, "小鸭");
+ printf("\n추가한 후의 해시 테이블:\n키 -> 값\n");
+ print(hashmap);
+
+ // 조회 연산
+ // 해시 테이블에 키 key를 입력하여 값 val을 얻는다
+ char *name = get(hashmap, 13276);
+ printf("\n학번 13276을 입력하여 조회한 이름: %s\n", name);
+
+ // 삭제 연산
+ // 해시 테이블에서 키-값 쌍 (key, val)을 삭제한다
+ removeItem(hashmap, 16750);
+ printf("\n16750을 삭제한 후의 해시 테이블\n키 -> 값\n");
+ print(hashmap);
+
+ // 해시 테이블을 해제한다
+ delHashMapOpenAddressing(hashmap);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_hashing/simple_hash.c b/ko/codes/c/chapter_hashing/simple_hash.c
new file mode 100644
index 000000000..ae18d1c36
--- /dev/null
+++ b/ko/codes/c/chapter_hashing/simple_hash.c
@@ -0,0 +1,68 @@
+/**
+ * File: simple_hash.c
+ * Created Time: 2023-09-09
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 덧셈 해시 */
+int addHash(char *key) {
+ long long hash = 0;
+ const int MODULUS = 1000000007;
+ for (int i = 0; i < strlen(key); i++) {
+ hash = (hash + (unsigned char)key[i]) % MODULUS;
+ }
+ return (int)hash;
+}
+
+/* 곱셈 해시 */
+int mulHash(char *key) {
+ long long hash = 0;
+ const int MODULUS = 1000000007;
+ for (int i = 0; i < strlen(key); i++) {
+ hash = (31 * hash + (unsigned char)key[i]) % MODULUS;
+ }
+ return (int)hash;
+}
+
+/* XOR 해시 */
+int xorHash(char *key) {
+ int hash = 0;
+ const int MODULUS = 1000000007;
+
+ for (int i = 0; i < strlen(key); i++) {
+ hash ^= (unsigned char)key[i];
+ }
+ return hash & MODULUS;
+}
+
+/* 회전 해시 */
+int rotHash(char *key) {
+ long long hash = 0;
+ const int MODULUS = 1000000007;
+ for (int i = 0; i < strlen(key); i++) {
+ hash = ((hash << 4) ^ (hash >> 28) ^ (unsigned char)key[i]) % MODULUS;
+ }
+
+ return (int)hash;
+}
+
+/* 실행 코드 */
+int main() {
+ char *key = "Hello 算法";
+
+ int hash = addHash(key);
+ printf("덧셈 해시값은 %d이다\n", hash);
+
+ hash = mulHash(key);
+ printf("곱셈 해시값은 %d이다\n", hash);
+
+ hash = xorHash(key);
+ printf("XOR 해시값은 %d이다\n", hash);
+
+ hash = rotHash(key);
+ printf("회전 해시값은 %d이다\n", hash);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_heap/CMakeLists.txt b/ko/codes/c/chapter_heap/CMakeLists.txt
new file mode 100644
index 000000000..357ec702c
--- /dev/null
+++ b/ko/codes/c/chapter_heap/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_executable(my_heap_test my_heap_test.c)
+add_executable(top_k top_k.c)
diff --git a/ko/codes/c/chapter_heap/my_heap.c b/ko/codes/c/chapter_heap/my_heap.c
new file mode 100644
index 000000000..b3b53c841
--- /dev/null
+++ b/ko/codes/c/chapter_heap/my_heap.c
@@ -0,0 +1,152 @@
+/**
+ * File: my_heap.c
+ * Created Time: 2023-01-15
+ * Author: Reanon (793584285@qq.com)
+ */
+
+#include "../utils/common.h"
+
+#define MAX_SIZE 5000
+
+/* 최대 힙 */
+typedef struct {
+ // size는 실제 원소 개수를 나타낸다
+ int size;
+ // 메모리를 미리 할당한 배열을 사용하여 용량 확장을 피한다
+ int data[MAX_SIZE];
+} MaxHeap;
+
+// 함수 선언
+void siftDown(MaxHeap *maxHeap, int i);
+void siftUp(MaxHeap *maxHeap, int i);
+int parent(MaxHeap *maxHeap, int i);
+
+/* 생성자: 슬라이스를 바탕으로 힙 구성 */
+MaxHeap *newMaxHeap(int nums[], int size) {
+ // 모든 원소를 힙에 삽입한다
+ MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap));
+ maxHeap->size = size;
+ memcpy(maxHeap->data, nums, size * sizeof(int));
+ for (int i = parent(maxHeap, size - 1); i >= 0; i--) {
+ // 리프 노드를 제외한 모든 노드에 힙 재정렬을 수행한다
+ siftDown(maxHeap, i);
+ }
+ return maxHeap;
+}
+
+/* 소멸자 */
+void delMaxHeap(MaxHeap *maxHeap) {
+ // 메모리 해제
+ free(maxHeap);
+}
+
+/* 왼쪽 자식 노드의 인덱스 반환 */
+int left(MaxHeap *maxHeap, int i) {
+ return 2 * i + 1;
+}
+
+/* 오른쪽 자식 노드의 인덱스 반환 */
+int right(MaxHeap *maxHeap, int i) {
+ return 2 * i + 2;
+}
+
+/* 부모 노드의 인덱스 반환 */
+int parent(MaxHeap *maxHeap, int i) {
+ return (i - 1) / 2; // 내림
+}
+
+/* 원소 교환 */
+void swap(MaxHeap *maxHeap, int i, int j) {
+ int temp = maxHeap->data[i];
+ maxHeap->data[i] = maxHeap->data[j];
+ maxHeap->data[j] = temp;
+}
+
+/* 힙 크기 반환 */
+int size(MaxHeap *maxHeap) {
+ return maxHeap->size;
+}
+
+/* 힙이 비어 있는지 판단 */
+int isEmpty(MaxHeap *maxHeap) {
+ return maxHeap->size == 0;
+}
+
+/* 힙의 루트 원소 접근 */
+int peek(MaxHeap *maxHeap) {
+ return maxHeap->data[0];
+}
+
+/* 힙에 원소 삽입 */
+void push(MaxHeap *maxHeap, int val) {
+ // 기본적으로 이렇게 많은 노드를 추가해서는 안 된다
+ if (maxHeap->size == MAX_SIZE) {
+ printf("힙이 가득 찼다!");
+ return;
+ }
+ // 노드 추가
+ maxHeap->data[maxHeap->size] = val;
+ maxHeap->size++;
+
+ // 아래에서 위로 힙을 재정렬한다
+ siftUp(maxHeap, maxHeap->size - 1);
+}
+
+/* 힙에서 원소 꺼내기 */
+int pop(MaxHeap *maxHeap) {
+ // 비어 있는 경우를 처리한다
+ if (isEmpty(maxHeap)) {
+ printf("힙이 비어 있다!");
+ return INT_MAX;
+ }
+ // 루트 노드와 가장 오른쪽 리프 노드를 교환한다(첫 원소와 마지막 원소 교환)
+ swap(maxHeap, 0, size(maxHeap) - 1);
+ // 노드 삭제
+ int val = maxHeap->data[maxHeap->size - 1];
+ maxHeap->size--;
+ // 위에서 아래로 힙을 재정렬한다
+ siftDown(maxHeap, 0);
+
+ // 힙의 루트 원소를 반환한다
+ return val;
+}
+
+/* 노드 i에서 시작하여 위에서 아래로 힙 재정렬 */
+void siftDown(MaxHeap *maxHeap, int i) {
+ while (true) {
+ // 노드 i, l, r 중 값이 가장 큰 노드를 찾아 max로 표시한다
+ int l = left(maxHeap, i);
+ int r = right(maxHeap, i);
+ int max = i;
+ if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) {
+ max = l;
+ }
+ if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) {
+ max = r;
+ }
+ // 노드 i의 값이 가장 크거나 인덱스 l, r이 범위를 벗어나면 힙을 더 재정렬할 필요가 없으므로 종료한다
+ if (max == i) {
+ break;
+ }
+ // 두 노드를 교환한다
+ swap(maxHeap, i, max);
+ // 아래쪽으로 힙 재정렬을 반복한다
+ i = max;
+ }
+}
+
+/* 노드 i에서 시작하여 아래에서 위로 힙 재정렬 */
+void siftUp(MaxHeap *maxHeap, int i) {
+ while (true) {
+ // 노드 i의 부모 노드를 구한다
+ int p = parent(maxHeap, i);
+ // 루트 노드를 넘어가거나 노드를 수정할 필요가 없으면 힙 재정렬을 마친다
+ if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) {
+ break;
+ }
+ // 두 노드를 교환한다
+ swap(maxHeap, i, p);
+ // 위쪽으로 힙 재정렬을 반복한다
+ i = p;
+ }
+}
diff --git a/ko/codes/c/chapter_heap/my_heap_test.c b/ko/codes/c/chapter_heap/my_heap_test.c
new file mode 100644
index 000000000..cc6d71cfb
--- /dev/null
+++ b/ko/codes/c/chapter_heap/my_heap_test.c
@@ -0,0 +1,41 @@
+/**
+ * File: my_heap_test.c
+ * Created Time: 2023-01-15
+ * Author: Reanon (793584285@qq.com)
+ */
+
+#include "my_heap.c"
+
+/* 실행 코드 */
+int main() {
+ /* 힙 초기화 */
+ // 최대 힙 초기화
+ int nums[] = {9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2};
+ MaxHeap *maxHeap = newMaxHeap(nums, sizeof(nums) / sizeof(int));
+ printf("배열을 입력하여 힙을 구성한 후\n");
+ printHeap(maxHeap->data, maxHeap->size);
+
+ /* 힙의 루트 원소 반환 */
+ printf("\n힙의 루트 원소: %d\n", peek(maxHeap));
+
+ /* 힙에 원소 삽입 */
+ push(maxHeap, 7);
+ printf("\n원소 7을 힙에 삽입한 후\n");
+ printHeap(maxHeap->data, maxHeap->size);
+
+ /* 힙의 루트 원소 꺼내기 */
+ int top = pop(maxHeap);
+ printf("\n힙의 루트 원소를 꺼낸 후(원소: %d)\n", top);
+ printHeap(maxHeap->data, maxHeap->size);
+
+ /* 힙 크기 반환 */
+ printf("\n힙의 원소 개수: %d\n", size(maxHeap));
+
+ /* 힙이 비어 있는지 판단 */
+ printf("\n힙이 비어 있는가: %d\n", isEmpty(maxHeap));
+
+ // 메모리 해제
+ delMaxHeap(maxHeap);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_heap/top_k.c b/ko/codes/c/chapter_heap/top_k.c
new file mode 100644
index 000000000..8aa936696
--- /dev/null
+++ b/ko/codes/c/chapter_heap/top_k.c
@@ -0,0 +1,73 @@
+/**
+ * File: top_k.c
+ * Created Time: 2023-10-26
+ * Author: krahets (krahets163.com)
+ */
+
+#include "my_heap.c"
+
+/* 힙에 원소 삽입 */
+void pushMinHeap(MaxHeap *maxHeap, int val) {
+ // 원소의 부호를 반전한다
+ push(maxHeap, -val);
+}
+
+/* 힙에서 원소 꺼내기 */
+int popMinHeap(MaxHeap *maxHeap) {
+ // 원소의 부호를 반전한다
+ return -pop(maxHeap);
+}
+
+/* 힙의 루트 원소 접근 */
+int peekMinHeap(MaxHeap *maxHeap) {
+ // 원소의 부호를 반전한다
+ return -peek(maxHeap);
+}
+
+/* 힙의 원소를 반환한다 */
+int *getMinHeap(MaxHeap *maxHeap) {
+ // 힙의 모든 원소의 부호를 반전하여 res 배열에 저장한다
+ int *res = (int *)malloc(maxHeap->size * sizeof(int));
+ for (int i = 0; i < maxHeap->size; i++) {
+ res[i] = -maxHeap->data[i];
+ }
+ return res;
+}
+
+// 힙으로 배열에서 가장 큰 k개 원소를 찾는 함수
+int *topKHeap(int *nums, int sizeNums, int k) {
+ // 최소 힙 초기화
+ // 힙의 모든 원소의 부호를 반전하여 최대 힙으로 최소 힙을 구현한다는 점에 유의한다
+ int *empty = (int *)malloc(0);
+ MaxHeap *maxHeap = newMaxHeap(empty, 0);
+ // 배열의 앞쪽 k개 원소를 힙에 삽입한다
+ for (int i = 0; i < k; i++) {
+ pushMinHeap(maxHeap, nums[i]);
+ }
+ // k+1번째 원소부터 힙의 길이를 k로 유지한다
+ for (int i = k; i < sizeNums; i++) {
+ // 현재 원소가 힙의 루트 원소보다 크면 루트 원소를 꺼내고 현재 원소를 힙에 삽입한다
+ if (nums[i] > peekMinHeap(maxHeap)) {
+ popMinHeap(maxHeap);
+ pushMinHeap(maxHeap, nums[i]);
+ }
+ }
+ int *res = getMinHeap(maxHeap);
+ // 메모리 해제
+ delMaxHeap(maxHeap);
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ int nums[] = {1, 7, 6, 3, 2};
+ int k = 3;
+ int sizeNums = sizeof(nums) / sizeof(nums[0]);
+
+ int *res = topKHeap(nums, sizeNums, k);
+ printf("가장 큰 %d개 원소: ", k);
+ printArray(res, k);
+
+ free(res);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_searching/CMakeLists.txt b/ko/codes/c/chapter_searching/CMakeLists.txt
new file mode 100644
index 000000000..7b2a152d5
--- /dev/null
+++ b/ko/codes/c/chapter_searching/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_executable(binary_search binary_search.c)
+add_executable(two_sum two_sum.c)
+add_executable(binary_search_edge binary_search_edge.c)
+add_executable(binary_search_insertion binary_search_insertion.c)
diff --git a/ko/codes/c/chapter_searching/binary_search.c b/ko/codes/c/chapter_searching/binary_search.c
new file mode 100644
index 000000000..b96b821ee
--- /dev/null
+++ b/ko/codes/c/chapter_searching/binary_search.c
@@ -0,0 +1,59 @@
+/**
+ * File: binary_search.c
+ * Created Time: 2023-03-18
+ * Author: Guanngxu (446678850@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 이진 탐색(양 끝을 포함하는 닫힌 구간) */
+int binarySearch(int *nums, int len, int target) {
+ // 닫힌 구간 [0, n-1]을 초기화한다. i와 j는 각각 배열의 첫 원소와 마지막 원소를 가리킨다
+ int i = 0, j = len - 1;
+ // 반복하다 탐색 구간이 비면 종료한다(i > j이면 빈 구간이다)
+ while (i <= j) {
+ int m = i + (j - i) / 2; // 중간 인덱스 m을 계산한다
+ if (nums[m] < target) // 이 경우 target은 구간 [m+1, j]에 있다
+ i = m + 1;
+ else if (nums[m] > target) // 이 경우 target은 구간 [i, m-1]에 있다
+ j = m - 1;
+ else // 목표 원소를 찾았으므로 해당 인덱스를 반환한다
+ return m;
+ }
+ // 목표 원소를 찾지 못했으므로 -1을 반환한다
+ return -1;
+}
+
+/* 이진 탐색(왼쪽 끝은 포함하고 오른쪽 끝은 제외하는 구간) */
+int binarySearchLCRO(int *nums, int len, int target) {
+ // 왼쪽 끝만 포함하는 구간 [0, n)을 초기화한다. i는 배열의 첫 원소를, j는 마지막 원소 바로 다음 위치를 가리킨다
+ int i = 0, j = len;
+ // 반복하다 탐색 구간이 비면 종료한다(i = j이면 빈 구간이다)
+ while (i < j) {
+ int m = i + (j - i) / 2; // 중간 인덱스 m을 계산한다
+ if (nums[m] < target) // 이 경우 target은 구간 [m+1, j)에 있다
+ i = m + 1;
+ else if (nums[m] > target) // 이 경우 target은 구간 [i, m)에 있다
+ j = m;
+ else // 목표 원소를 찾았으므로 해당 인덱스를 반환한다
+ return m;
+ }
+ // 목표 원소를 찾지 못했으므로 -1을 반환한다
+ return -1;
+}
+
+/* 실행 코드 */
+int main() {
+ int target = 6;
+ int nums[10] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35};
+
+ /* 이진 탐색(양 끝을 포함하는 닫힌 구간) */
+ int index = binarySearch(nums, 10, target);
+ printf("목표 원소 6의 인덱스 = %d\n", index);
+
+ /* 이진 탐색(왼쪽 끝은 포함하고 오른쪽 끝은 제외하는 구간) */
+ index = binarySearchLCRO(nums, 10, target);
+ printf("목표 원소 6의 인덱스 = %d\n", index);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_searching/binary_search_edge.c b/ko/codes/c/chapter_searching/binary_search_edge.c
new file mode 100644
index 000000000..26607004c
--- /dev/null
+++ b/ko/codes/c/chapter_searching/binary_search_edge.c
@@ -0,0 +1,67 @@
+/**
+ * File: binary_search_edge.c
+ * Created Time: 2023-09-09
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 삽입 위치의 이진 탐색(중복 원소 있음) */
+int binarySearchInsertion(int *nums, int numSize, int target) {
+ int i = 0, j = numSize - 1; // 닫힌 구간 [0, n-1]을 초기화한다
+ while (i <= j) {
+ int m = i + (j - i) / 2; // 중간 인덱스 m을 계산한다
+ if (nums[m] < target) {
+ i = m + 1; // target은 구간 [m+1, j]에 있다
+ } else {
+ j = m - 1; // target보다 작은 가장 오른쪽 원소는 구간 [i, m-1]에 있다
+ }
+ }
+ // 삽입 위치 i를 반환한다
+ return i;
+}
+
+/* 가장 왼쪽에 있는 target을 이진 탐색 */
+int binarySearchLeftEdge(int *nums, int numSize, int target) {
+ // target의 삽입 위치를 찾는 것과 같다
+ int i = binarySearchInsertion(nums, numSize, target);
+ // target을 찾지 못했으므로 -1을 반환한다
+ if (i == numSize || nums[i] != target) {
+ return -1;
+ }
+ // target을 찾았으므로 인덱스 i를 반환한다
+ return i;
+}
+
+/* 가장 오른쪽에 있는 target을 이진 탐색 */
+int binarySearchRightEdge(int *nums, int numSize, int target) {
+ // 가장 왼쪽에 있는 target + 1을 찾는 문제로 바꾼다
+ int i = binarySearchInsertion(nums, numSize, target + 1);
+ // j는 가장 오른쪽의 target을 가리키고, i는 target보다 큰 첫 번째 원소를 가리킨다
+ int j = i - 1;
+ // target을 찾지 못했으므로 -1을 반환한다
+ if (j == -1 || nums[j] != target) {
+ return -1;
+ }
+ // target을 찾았으므로 인덱스 j를 반환한다
+ return j;
+}
+
+/* 실행 코드 */
+int main() {
+ // 중복 원소를 포함하는 배열
+ int nums[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15};
+ printf("\n배열 nums = ");
+ printArray(nums, sizeof(nums) / sizeof(nums[0]));
+
+ // 왼쪽 경계와 오른쪽 경계의 이진 탐색
+ int targets[] = {6, 7};
+ for (int i = 0; i < sizeof(targets) / sizeof(targets[0]); i++) {
+ int index = binarySearchLeftEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]);
+ printf("가장 왼쪽에 있는 원소 %d의 인덱스는 %d이다\n", targets[i], index);
+ index = binarySearchRightEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]);
+ printf("가장 오른쪽에 있는 원소 %d의 인덱스는 %d이다\n", targets[i], index);
+ }
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_searching/binary_search_insertion.c b/ko/codes/c/chapter_searching/binary_search_insertion.c
new file mode 100644
index 000000000..79c42dccb
--- /dev/null
+++ b/ko/codes/c/chapter_searching/binary_search_insertion.c
@@ -0,0 +1,68 @@
+/**
+ * File: binary_search_insertion.c
+ * Created Time: 2023-09-09
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 삽입 위치의 이진 탐색(중복 원소 없음) */
+int binarySearchInsertionSimple(int *nums, int numSize, int target) {
+ int i = 0, j = numSize - 1; // 닫힌 구간 [0, n-1]을 초기화한다
+ while (i <= j) {
+ int m = i + (j - i) / 2; // 중간 인덱스 m을 계산한다
+ if (nums[m] < target) {
+ i = m + 1; // target은 구간 [m+1, j]에 있다
+ } else if (nums[m] > target) {
+ j = m - 1; // target은 구간 [i, m-1]에 있다
+ } else {
+ return m; // target을 찾았으므로 삽입 위치 m을 반환한다
+ }
+ }
+ // target을 찾지 못했으므로 삽입 위치 i를 반환한다
+ return i;
+}
+
+/* 삽입 위치의 이진 탐색(중복 원소 있음) */
+int binarySearchInsertion(int *nums, int numSize, int target) {
+ int i = 0, j = numSize - 1; // 닫힌 구간 [0, n-1]을 초기화한다
+ while (i <= j) {
+ int m = i + (j - i) / 2; // 중간 인덱스 m을 계산한다
+ if (nums[m] < target) {
+ i = m + 1; // target은 구간 [m+1, j]에 있다
+ } else if (nums[m] > target) {
+ j = m - 1; // target은 구간 [i, m-1]에 있다
+ } else {
+ j = m - 1; // target보다 작은 가장 오른쪽 원소는 구간 [i, m-1]에 있다
+ }
+ }
+ // 삽입 위치 i를 반환한다
+ return i;
+}
+
+/* 실행 코드 */
+int main() {
+ // 중복 원소가 없는 배열
+ int nums1[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35};
+ printf("\n배열 nums = ");
+ printArray(nums1, sizeof(nums1) / sizeof(nums1[0]));
+ // 삽입 위치의 이진 탐색
+ int targets1[] = {6, 9};
+ for (int i = 0; i < sizeof(targets1) / sizeof(targets1[0]); i++) {
+ int index = binarySearchInsertionSimple(nums1, sizeof(nums1) / sizeof(nums1[0]), targets1[i]);
+ printf("원소 %d의 삽입 위치 인덱스는 %d이다\n", targets1[i], index);
+ }
+
+ // 중복 원소를 포함하는 배열
+ int nums2[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15};
+ printf("\n배열 nums = ");
+ printArray(nums2, sizeof(nums2) / sizeof(nums2[0]));
+ // 삽입 위치의 이진 탐색
+ int targets2[] = {2, 6, 20};
+ for (int i = 0; i < sizeof(targets2) / sizeof(int); i++) {
+ int index = binarySearchInsertion(nums2, sizeof(nums2) / sizeof(nums2[0]), targets2[i]);
+ printf("원소 %d의 삽입 위치 인덱스는 %d이다\n", targets2[i], index);
+ }
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_searching/two_sum.c b/ko/codes/c/chapter_searching/two_sum.c
new file mode 100644
index 000000000..041d5fa18
--- /dev/null
+++ b/ko/codes/c/chapter_searching/two_sum.c
@@ -0,0 +1,86 @@
+/**
+ * File: two_sum.c
+ * Created Time: 2023-01-19
+ * Author: Reanon (793584285@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 방법 1: 완전 열거 */
+int *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) {
+ for (int i = 0; i < numsSize; ++i) {
+ for (int j = i + 1; j < numsSize; ++j) {
+ if (nums[i] + nums[j] == target) {
+ int *res = malloc(sizeof(int) * 2);
+ res[0] = i, res[1] = j;
+ *returnSize = 2;
+ return res;
+ }
+ }
+ }
+ *returnSize = 0;
+ return NULL;
+}
+
+/* 해시 테이블 */
+typedef struct {
+ int key;
+ int val;
+ UT_hash_handle hh; // uthash.h를 기반으로 구현한다
+} HashTable;
+
+/* 해시 테이블 조회 */
+HashTable *find(HashTable *h, int key) {
+ HashTable *tmp;
+ HASH_FIND_INT(h, &key, tmp);
+ return tmp;
+}
+
+/* 해시 테이블에 원소 삽입 */
+void insert(HashTable **h, int key, int val) {
+ HashTable *t = find(*h, key);
+ if (t == NULL) {
+ HashTable *tmp = malloc(sizeof(HashTable));
+ tmp->key = key, tmp->val = val;
+ HASH_ADD_INT(*h, key, tmp);
+ } else {
+ t->val = val;
+ }
+}
+
+/* 방법 2: 보조 해시 테이블 */
+int *twoSumHashTable(int *nums, int numsSize, int target, int *returnSize) {
+ HashTable *hashtable = NULL;
+ for (int i = 0; i < numsSize; i++) {
+ HashTable *t = find(hashtable, target - nums[i]);
+ if (t != NULL) {
+ int *res = malloc(sizeof(int) * 2);
+ res[0] = t->val, res[1] = i;
+ *returnSize = 2;
+ return res;
+ }
+ insert(&hashtable, nums[i], i);
+ }
+ *returnSize = 0;
+ return NULL;
+}
+
+/* 실행 코드 */
+int main() {
+ // ======= 테스트 사례 =======
+ int nums[] = {2, 7, 11, 15};
+ int target = 13;
+ // ====== 실행 코드 ======
+ int returnSize;
+ int *res = twoSumBruteForce(nums, sizeof(nums) / sizeof(int), target, &returnSize);
+ // 방법 1
+ printf("방법 1 res = ");
+ printArray(res, returnSize);
+
+ // 방법 2
+ res = twoSumHashTable(nums, sizeof(nums) / sizeof(int), target, &returnSize);
+ printf("방법 2 res = ");
+ printArray(res, returnSize);
+
+ return 0;
+}
\ No newline at end of file
diff --git a/ko/codes/c/chapter_sorting/CMakeLists.txt b/ko/codes/c/chapter_sorting/CMakeLists.txt
new file mode 100644
index 000000000..88756b4c9
--- /dev/null
+++ b/ko/codes/c/chapter_sorting/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_executable(bubble_sort bubble_sort.c)
+add_executable(insertion_sort insertion_sort.c)
+add_executable(quick_sort quick_sort.c)
+add_executable(counting_sort counting_sort.c)
+add_executable(radix_sort radix_sort.c)
+add_executable(merge_sort merge_sort.c)
+add_executable(heap_sort heap_sort.c)
+add_executable(bucket_sort bucket_sort.c)
+add_executable(selection_sort selection_sort.c)
diff --git a/ko/codes/c/chapter_sorting/bubble_sort.c b/ko/codes/c/chapter_sorting/bubble_sort.c
new file mode 100644
index 000000000..5b40f78f6
--- /dev/null
+++ b/ko/codes/c/chapter_sorting/bubble_sort.c
@@ -0,0 +1,61 @@
+/**
+ * File: bubble_sort.c
+ * Created Time: 2022-12-26
+ * Author: Listening (https://github.com/L-Super)
+ */
+
+#include "../utils/common.h"
+
+/* 버블 정렬 */
+void bubbleSort(int nums[], int size) {
+ // 바깥쪽 반복문: 정렬되지 않은 구간은 [0, i]이다
+ for (int i = size - 1; i > 0; i--) {
+ // 안쪽 반복문: 정렬되지 않은 구간 [0, i]의 최댓값을 교환하여 이 구간의 맨 오른쪽으로 옮긴다
+ for (int j = 0; j < i; j++) {
+ if (nums[j] > nums[j + 1]) {
+ int temp = nums[j];
+ nums[j] = nums[j + 1];
+ nums[j + 1] = temp;
+ }
+ }
+ }
+}
+
+/* 버블 정렬(플래그 최적화)*/
+void bubbleSortWithFlag(int nums[], int size) {
+ // 바깥쪽 반복문: 정렬되지 않은 구간은 [0, i]이다
+ for (int i = size - 1; i > 0; i--) {
+ bool flag = false;
+ // 안쪽 반복문: 정렬되지 않은 구간 [0, i]의 최댓값을 교환하여 이 구간의 맨 오른쪽으로 옮긴다
+ for (int j = 0; j < i; j++) {
+ if (nums[j] > nums[j + 1]) {
+ int temp = nums[j];
+ nums[j] = nums[j + 1];
+ nums[j + 1] = temp;
+ flag = true;
+ }
+ }
+ if (!flag)
+ break;
+ }
+}
+
+/* 실행 코드 */
+int main() {
+ int nums[6] = {4, 1, 3, 1, 5, 2};
+ printf("버블 정렬 후: ");
+ bubbleSort(nums, 6);
+ for (int i = 0; i < 6; i++) {
+ printf("%d ", nums[i]);
+ }
+
+ int nums1[6] = {4, 1, 3, 1, 5, 2};
+ printf("\n최적화한 버블 정렬 후: ");
+ bubbleSortWithFlag(nums1, 6);
+ for (int i = 0; i < 6; i++) {
+ printf("%d ", nums1[i]);
+ }
+ printf("\n");
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_sorting/bucket_sort.c b/ko/codes/c/chapter_sorting/bucket_sort.c
new file mode 100644
index 000000000..5419a8ed6
--- /dev/null
+++ b/ko/codes/c/chapter_sorting/bucket_sort.c
@@ -0,0 +1,57 @@
+/**
+ * File: bucket_sort.c
+ * Created Time: 2023-05-30
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+#define SIZE 10
+
+/* qsort용 비교 함수 */
+int compare(const void *a, const void *b) {
+ float fa = *(const float *)a;
+ float fb = *(const float *)b;
+ return (fa > fb) - (fa < fb);
+}
+
+/* 버킷 정렬 */
+void bucketSort(float nums[], int n) {
+ int k = n / 2; // k = n/2개의 버킷을 초기화한다
+ int *sizes = malloc(k * sizeof(int)); // 각 버킷의 크기를 기록한다
+ float **buckets = malloc(k * sizeof(float *)); // 동적 배열의 배열(버킷)
+ // 각 버킷에 충분한 공간을 미리 할당한다
+ for (int i = 0; i < k; ++i) {
+ buckets[i] = (float *)malloc(n * sizeof(float));
+ sizes[i] = 0;
+ }
+ // 1. 배열 원소를 각 버킷에 배정한다
+ for (int i = 0; i < n; ++i) {
+ int idx = (int)(nums[i] * k);
+ buckets[idx][sizes[idx]++] = nums[i];
+ }
+ // 2. 각 버킷을 정렬한다
+ for (int i = 0; i < k; ++i) {
+ qsort(buckets[i], sizes[i], sizeof(float), compare);
+ }
+ // 3. 정렬한 버킷을 병합한다
+ int idx = 0;
+ for (int i = 0; i < k; ++i) {
+ for (int j = 0; j < sizes[i]; ++j) {
+ nums[idx++] = buckets[i][j];
+ }
+ // 메모리 해제
+ free(buckets[i]);
+ }
+}
+
+/* 실행 코드 */
+int main() {
+ // 입력 데이터는 범위가 [0, 1)인 부동 소수점 수라고 가정한다
+ float nums[SIZE] = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f};
+ bucketSort(nums, SIZE);
+ printf("버킷 정렬을 마친 후 nums = ");
+ printArrayFloat(nums, SIZE);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_sorting/counting_sort.c b/ko/codes/c/chapter_sorting/counting_sort.c
new file mode 100644
index 000000000..e0a2179d7
--- /dev/null
+++ b/ko/codes/c/chapter_sorting/counting_sort.c
@@ -0,0 +1,87 @@
+/**
+ * File: counting_sort.c
+ * Created Time: 2023-03-20
+ * Author: Reanon (793584285@qq.com), Guanngxu (446678850@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 계수 정렬 */
+// 간단한 구현이며, 객체 정렬에는 사용할 수 없다
+void countingSortNaive(int nums[], int size) {
+ // 1. 배열의 최댓값 m을 구한다
+ int m = 0;
+ for (int i = 0; i < size; i++) {
+ if (nums[i] > m) {
+ m = nums[i];
+ }
+ }
+ // 2. 각 숫자의 등장 횟수를 센다
+ // counter[num]은 num의 등장 횟수를 나타낸다
+ int *counter = calloc(m + 1, sizeof(int));
+ for (int i = 0; i < size; i++) {
+ counter[nums[i]]++;
+ }
+ // 3. counter를 순회하며 각 원소를 원래 배열 nums에 채운다
+ int i = 0;
+ for (int num = 0; num < m + 1; num++) {
+ for (int j = 0; j < counter[num]; j++, i++) {
+ nums[i] = num;
+ }
+ }
+ // 4. 메모리를 해제한다
+ free(counter);
+}
+
+/* 계수 정렬 */
+// 완전한 구현이며, 객체를 정렬할 수 있고 안정 정렬이다
+void countingSort(int nums[], int size) {
+ // 1. 배열의 최댓값 m을 구한다
+ int m = 0;
+ for (int i = 0; i < size; i++) {
+ if (nums[i] > m) {
+ m = nums[i];
+ }
+ }
+ // 2. 각 숫자의 등장 횟수를 센다
+ // counter[num]은 num의 등장 횟수를 나타낸다
+ int *counter = calloc(m, sizeof(int));
+ for (int i = 0; i < size; i++) {
+ counter[nums[i]]++;
+ }
+ // 3. counter의 접두 합을 구하여 등장 횟수를 마지막 인덱스로 변환한다
+ // 즉, counter[num]-1은 res에서 num이 마지막으로 등장하는 인덱스이다
+ for (int i = 0; i < m; i++) {
+ counter[i + 1] += counter[i];
+ }
+ // 4. nums를 역순으로 순회하며 각 원소를 결과 배열 res에 채운다
+ // 결과를 기록할 배열 res를 초기화한다
+ int *res = malloc(sizeof(int) * size);
+ for (int i = size - 1; i >= 0; i--) {
+ int num = nums[i];
+ res[counter[num] - 1] = num; // num을 해당 인덱스에 배치한다
+ counter[num]--; // 접두 합을 1 감소시켜 다음에 num을 배치할 인덱스를 구한다
+ }
+ // 결과 배열 res로 원래 배열 nums를 덮어쓴다
+ memcpy(nums, res, size * sizeof(int));
+ // 5. 메모리를 해제한다
+ free(res);
+ free(counter);
+}
+
+/* 실행 코드 */
+int main() {
+ int nums[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4};
+ int size = sizeof(nums) / sizeof(int);
+ countingSortNaive(nums, size);
+ printf("계수 정렬(객체 정렬 불가)을 마친 후 nums = ");
+ printArray(nums, size);
+
+ int nums1[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4};
+ int size1 = sizeof(nums1) / sizeof(int);
+ countingSort(nums1, size1);
+ printf("계수 정렬을 마친 후 nums1 = ");
+ printArray(nums1, size1);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_sorting/heap_sort.c b/ko/codes/c/chapter_sorting/heap_sort.c
new file mode 100644
index 000000000..dca486f46
--- /dev/null
+++ b/ko/codes/c/chapter_sorting/heap_sort.c
@@ -0,0 +1,60 @@
+/**
+ * File: heap_sort.c
+ * Created Time: 2023-05-30
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 길이가 n인 힙에서 노드 i부터 위에서 아래로 힙 재정렬 */
+void siftDown(int nums[], int n, int i) {
+ while (1) {
+ // 노드 i, l, r 중 값이 가장 큰 노드를 찾아 ma로 표시한다
+ int l = 2 * i + 1;
+ int r = 2 * i + 2;
+ int ma = i;
+ if (l < n && nums[l] > nums[ma])
+ ma = l;
+ if (r < n && nums[r] > nums[ma])
+ ma = r;
+ // 노드 i의 값이 가장 크거나 인덱스 l, r이 범위를 벗어나면 힙을 더 재정렬할 필요가 없으므로 종료한다
+ if (ma == i) {
+ break;
+ }
+ // 두 노드를 교환한다
+ int temp = nums[i];
+ nums[i] = nums[ma];
+ nums[ma] = temp;
+ // 아래쪽으로 힙 재정렬을 반복한다
+ i = ma;
+ }
+}
+
+/* 힙 정렬 */
+void heapSort(int nums[], int n) {
+ // 힙 구성 연산: 리프 노드를 제외한 모든 노드에 힙 재정렬을 수행한다
+ for (int i = n / 2 - 1; i >= 0; --i) {
+ siftDown(nums, n, i);
+ }
+ // 힙에서 최댓값을 꺼내는 작업을 n-1회 반복한다
+ for (int i = n - 1; i > 0; --i) {
+ // 루트 노드와 가장 오른쪽 리프 노드를 교환한다(첫 원소와 마지막 원소 교환)
+ int tmp = nums[0];
+ nums[0] = nums[i];
+ nums[i] = tmp;
+ // 루트 노드에서 시작하여 위에서 아래로 힙을 재정렬한다
+ siftDown(nums, i, 0);
+ }
+}
+
+/* 실행 코드 */
+int main() {
+ int nums[] = {4, 1, 3, 1, 5, 2};
+ int n = sizeof(nums) / sizeof(nums[0]);
+
+ heapSort(nums, n);
+ printf("힙 정렬을 마친 후 nums = ");
+ printArray(nums, n);
+
+ return 0;
+}
\ No newline at end of file
diff --git a/ko/codes/c/chapter_sorting/insertion_sort.c b/ko/codes/c/chapter_sorting/insertion_sort.c
new file mode 100644
index 000000000..058fa4594
--- /dev/null
+++ b/ko/codes/c/chapter_sorting/insertion_sort.c
@@ -0,0 +1,36 @@
+/**
+ * File: insertion_sort.c
+ * Created Time: 2022-12-29
+ * Author: Listening (https://github.com/L-Super)
+ */
+
+#include "../utils/common.h"
+
+/* 삽입 정렬 */
+void insertionSort(int nums[], int size) {
+ // 바깥쪽 반복문: 정렬된 구간은 [0, i-1]이다
+ for (int i = 1; i < size; i++) {
+ int base = nums[i], j = i - 1;
+ // 안쪽 반복문: base를 정렬된 구간 [0, i-1]의 올바른 위치에 삽입한다
+ while (j >= 0 && nums[j] > base) {
+ // nums[j]를 오른쪽으로 한 칸 이동한다
+ nums[j + 1] = nums[j];
+ j--;
+ }
+ // 올바른 위치에 base를 대입한다
+ nums[j + 1] = base;
+ }
+}
+
+/* 실행 코드 */
+int main() {
+ int nums[] = {4, 1, 3, 1, 5, 2};
+ insertionSort(nums, 6);
+ printf("삽입 정렬을 마친 후 nums = ");
+ for (int i = 0; i < 6; i++) {
+ printf("%d ", nums[i]);
+ }
+ printf("\n");
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_sorting/merge_sort.c b/ko/codes/c/chapter_sorting/merge_sort.c
new file mode 100644
index 000000000..e6718327e
--- /dev/null
+++ b/ko/codes/c/chapter_sorting/merge_sort.c
@@ -0,0 +1,63 @@
+/**
+ * File: merge_sort.c
+ * Created Time: 2022-03-21
+ * Author: Guanngxu (446678850@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 왼쪽 부분 배열과 오른쪽 부분 배열 병합 */
+void merge(int *nums, int left, int mid, int right) {
+ // 왼쪽 부분 배열의 구간은 [left, mid]이고, 오른쪽 부분 배열의 구간은 [mid+1, right]이다
+ // 병합 결과를 저장할 임시 배열 tmp를 생성한다
+ int tmpSize = right - left + 1;
+ int *tmp = (int *)malloc(tmpSize * sizeof(int));
+ // 왼쪽 부분 배열과 오른쪽 부분 배열의 시작 인덱스를 초기화한다
+ int i = left, j = mid + 1, k = 0;
+ // 양쪽 부분 배열 모두에 원소가 남아 있으면 비교하여 더 작은 원소를 임시 배열에 복사한다
+ while (i <= mid && j <= right) {
+ if (nums[i] <= nums[j]) {
+ tmp[k++] = nums[i++];
+ } else {
+ tmp[k++] = nums[j++];
+ }
+ }
+ // 왼쪽 부분 배열과 오른쪽 부분 배열의 남은 원소를 임시 배열에 복사한다
+ while (i <= mid) {
+ tmp[k++] = nums[i++];
+ }
+ while (j <= right) {
+ tmp[k++] = nums[j++];
+ }
+ // 임시 배열 tmp의 원소를 원래 배열 nums의 해당 구간에 다시 복사한다
+ for (k = 0; k < tmpSize; ++k) {
+ nums[left + k] = tmp[k];
+ }
+ // 메모리 해제
+ free(tmp);
+}
+
+/* 병합 정렬 */
+void mergeSort(int *nums, int left, int right) {
+ // 종료 조건
+ if (left >= right)
+ return; // 부분 배열의 길이가 1이면 재귀를 종료한다
+ // 분할 단계
+ int mid = left + (right - left) / 2; // 중간 위치를 계산한다
+ mergeSort(nums, left, mid); // 왼쪽 부분 배열에 재귀 호출한다
+ mergeSort(nums, mid + 1, right); // 오른쪽 부분 배열에 재귀 호출한다
+ // 병합 단계
+ merge(nums, left, mid, right);
+}
+
+/* 실행 코드 */
+int main() {
+ /* 병합 정렬 */
+ int nums[] = {7, 3, 2, 6, 0, 1, 5, 4};
+ int size = sizeof(nums) / sizeof(int);
+ mergeSort(nums, 0, size - 1);
+ printf("병합 정렬을 마친 후 nums = ");
+ printArray(nums, size);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_sorting/quick_sort.c b/ko/codes/c/chapter_sorting/quick_sort.c
new file mode 100644
index 000000000..95b578f98
--- /dev/null
+++ b/ko/codes/c/chapter_sorting/quick_sort.c
@@ -0,0 +1,137 @@
+/**
+ * File: quick_sort.c
+ * Created Time: 2023-01-18
+ * Author: Reanon (793584285@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 원소 교환 */
+void swap(int nums[], int i, int j) {
+ int tmp = nums[i];
+ nums[i] = nums[j];
+ nums[j] = tmp;
+}
+
+/* 센티널 분할 */
+int partition(int nums[], int left, int right) {
+ // nums[left]를 피벗으로 삼는다
+ int i = left, j = right;
+ while (i < j) {
+ while (i < j && nums[j] >= nums[left]) {
+ j--; // 오른쪽에서 왼쪽으로 피벗보다 작은 첫 번째 원소를 찾는다
+ }
+ while (i < j && nums[i] <= nums[left]) {
+ i++; // 왼쪽에서 오른쪽으로 피벗보다 큰 첫 번째 원소를 찾는다
+ }
+ // 두 원소를 교환한다
+ swap(nums, i, j);
+ }
+ // 피벗을 두 부분 배열의 경계로 교환하여 옮긴다
+ swap(nums, i, left);
+ // 피벗의 인덱스를 반환한다
+ return i;
+}
+
+/* 퀵 정렬 */
+void quickSort(int nums[], int left, int right) {
+ // 부분 배열의 길이가 1이면 재귀를 종료한다
+ if (left >= right) {
+ return;
+ }
+ // 센티널 분할
+ int pivot = partition(nums, left, right);
+ // 왼쪽 부분 배열과 오른쪽 부분 배열에 재귀 호출한다
+ quickSort(nums, left, pivot - 1);
+ quickSort(nums, pivot + 1, right);
+}
+
+// 다음은 중앙값으로 최적화한 퀵 정렬이다
+
+/* 세 후보 원소의 중앙값 선택 */
+int medianThree(int nums[], int left, int mid, int right) {
+ int l = nums[left], m = nums[mid], r = nums[right];
+ if ((l <= m && m <= r) || (r <= m && m <= l))
+ return mid; // m은 l과 r 사이에 있다
+ if ((m <= l && l <= r) || (r <= l && l <= m))
+ return left; // l은 m과 r 사이에 있다
+ return right;
+}
+
+/* 센티널 분할(세 수의 중앙값 선택) */
+int partitionMedian(int nums[], int left, int right) {
+ // 세 후보 원소의 중앙값 선택
+ int med = medianThree(nums, left, (left + right) / 2, right);
+ // 중앙값을 배열 맨 왼쪽으로 교환하여 옮긴다
+ swap(nums, left, med);
+ // nums[left]를 피벗으로 삼는다
+ int i = left, j = right;
+ while (i < j) {
+ while (i < j && nums[j] >= nums[left])
+ j--; // 오른쪽에서 왼쪽으로 피벗보다 작은 첫 번째 원소를 찾는다
+ while (i < j && nums[i] <= nums[left])
+ i++; // 왼쪽에서 오른쪽으로 피벗보다 큰 첫 번째 원소를 찾는다
+ swap(nums, i, j); // 두 원소를 교환한다
+ }
+ swap(nums, i, left); // 피벗을 두 부분 배열의 경계로 교환하여 옮긴다
+ return i; // 피벗의 인덱스를 반환한다
+}
+
+/* 퀵 정렬(세 수의 중앙값 선택) */
+void quickSortMedian(int nums[], int left, int right) {
+ // 부분 배열의 길이가 1이면 재귀를 종료한다
+ if (left >= right)
+ return;
+ // 센티널 분할
+ int pivot = partitionMedian(nums, left, right);
+ // 왼쪽 부분 배열과 오른쪽 부분 배열에 재귀 호출한다
+ quickSortMedian(nums, left, pivot - 1);
+ quickSortMedian(nums, pivot + 1, right);
+}
+
+// 다음은 재귀 깊이를 최적화한 퀵 정렬이다
+
+/* 퀵 정렬(재귀 깊이 최적화) */
+void quickSortTailCall(int nums[], int left, int right) {
+ // 부분 배열의 길이가 1이면 종료한다
+ while (left < right) {
+ // 센티널 분할 연산
+ int pivot = partition(nums, left, right);
+ // 두 부분 배열 중 더 짧은 쪽에 퀵 정렬을 수행한다
+ if (pivot - left < right - pivot) {
+ // 왼쪽 부분 배열을 재귀적으로 정렬한다
+ quickSortTailCall(nums, left, pivot - 1);
+ // 남은 미정렬 구간은 [pivot + 1, right]이다
+ left = pivot + 1;
+ } else {
+ // 오른쪽 부분 배열을 재귀적으로 정렬한다
+ quickSortTailCall(nums, pivot + 1, right);
+ // 남은 미정렬 구간은 [left, pivot - 1]이다
+ right = pivot - 1;
+ }
+ }
+}
+
+/* 실행 코드 */
+int main() {
+ /* 퀵 정렬 */
+ int nums[] = {2, 4, 1, 0, 3, 5};
+ int size = sizeof(nums) / sizeof(int);
+ quickSort(nums, 0, size - 1);
+ printf("퀵 정렬을 마친 후 nums = ");
+ printArray(nums, size);
+
+ /* 퀵 정렬(중앙값 피벗 최적화) */
+ int nums1[] = {2, 4, 1, 0, 3, 5};
+ quickSortMedian(nums1, 0, size - 1);
+ printf("퀵 정렬(중앙값 피벗 최적화)을 마친 후 nums = ");
+ printArray(nums1, size);
+
+ /* 퀵 정렬(재귀 깊이 최적화) */
+ int nums2[] = {2, 4, 1, 0, 3, 5};
+ quickSortTailCall(nums2, 0, size - 1);
+ printf("퀵 정렬(재귀 깊이 최적화)을 마친 후 nums = ");
+ printArray(nums1, size);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_sorting/radix_sort.c b/ko/codes/c/chapter_sorting/radix_sort.c
new file mode 100644
index 000000000..239085efe
--- /dev/null
+++ b/ko/codes/c/chapter_sorting/radix_sort.c
@@ -0,0 +1,75 @@
+/**
+ * File: radix_sort.c
+ * Created Time: 2023-01-18
+ * Author: Reanon (793584285@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 원소 num의 k번째 자릿값 반환. 이때 exp = 10^(k-1)이다 */
+int digit(int num, int exp) {
+ // k 대신 exp를 전달하면 여기서 비용이 큰 거듭제곱 계산을 반복하지 않아도 된다
+ return (num / exp) % 10;
+}
+
+/* 계수 정렬(nums의 k번째 자릿값을 기준으로 정렬) */
+void countingSortDigit(int nums[], int size, int exp) {
+ // 십진수의 각 자리는 0~9 범위이므로 길이가 10인 버킷 배열이 필요하다
+ int *counter = (int *)malloc((sizeof(int) * 10));
+ memset(counter, 0, sizeof(int) * 10); // 이후 메모리 해제를 지원하도록 0으로 초기화한다
+ // 0~9 각 숫자의 등장 횟수를 센다
+ for (int i = 0; i < size; i++) {
+ // nums[i]의 k번째 자릿값을 구하여 d로 표시한다
+ int d = digit(nums[i], exp);
+ // 숫자 d의 등장 횟수를 센다
+ counter[d]++;
+ }
+ // 접두 합을 구하여 등장 개수를 배열 인덱스로 변환한다
+ for (int i = 1; i < 10; i++) {
+ counter[i] += counter[i - 1];
+ }
+ // 역순으로 순회하며 버킷에 집계된 결과에 따라 각 원소를 res에 채운다
+ int *res = (int *)malloc(sizeof(int) * size);
+ for (int i = size - 1; i >= 0; i--) {
+ int d = digit(nums[i], exp);
+ int j = counter[d] - 1; // 배열에서 d의 인덱스 j를 구한다
+ res[j] = nums[i]; // 현재 원소를 인덱스 j에 채운다
+ counter[d]--; // d의 개수를 1 줄인다
+ }
+ // 결과로 원래 배열 nums를 덮어쓴다
+ for (int i = 0; i < size; i++) {
+ nums[i] = res[i];
+ }
+ // 메모리 해제
+ free(res);
+ free(counter);
+}
+
+/* 기수 정렬 */
+void radixSort(int nums[], int size) {
+ // 배열의 최댓값을 구하여 최대 자릿수를 판단한다
+ int max = INT32_MIN;
+ for (int i = 0; i < size; i++) {
+ if (nums[i] > max) {
+ max = nums[i];
+ }
+ }
+ // 낮은 자리에서 높은 자리 순서로 순회한다
+ for (int exp = 1; max >= exp; exp *= 10)
+ // 배열 원소의 k번째 자릿값에 대해 계수 정렬을 수행한다
+ // k = 1 -> exp = 1
+ // k = 2 -> exp = 10
+ // 즉, exp = 10^(k-1)이다
+ countingSortDigit(nums, size, exp);
+}
+
+/* 실행 코드 */
+int main() {
+ // 기수 정렬
+ int nums[] = {10546151, 35663510, 42865989, 34862445, 81883077,
+ 88906420, 72429244, 30524779, 82060337, 63832996};
+ int size = sizeof(nums) / sizeof(int);
+ radixSort(nums, size);
+ printf("기수 정렬을 마친 후 nums = ");
+ printArray(nums, size);
+}
diff --git a/ko/codes/c/chapter_sorting/selection_sort.c b/ko/codes/c/chapter_sorting/selection_sort.c
new file mode 100644
index 000000000..d991d9e96
--- /dev/null
+++ b/ko/codes/c/chapter_sorting/selection_sort.c
@@ -0,0 +1,37 @@
+/**
+ * File: selection_sort.c
+ * Created Time: 2023-05-31
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 선택 정렬 */
+void selectionSort(int nums[], int n) {
+ // 바깥쪽 반복문: 정렬되지 않은 구간은 [i, n-1]이다
+ for (int i = 0; i < n - 1; i++) {
+ // 안쪽 반복문: 정렬되지 않은 구간의 최솟값을 찾는다
+ int k = i;
+ for (int j = i + 1; j < n; j++) {
+ if (nums[j] < nums[k])
+ k = j; // 최솟값의 인덱스를 기록한다
+ }
+ // 이 최솟값과 정렬되지 않은 구간의 첫 원소를 교환한다
+ int temp = nums[i];
+ nums[i] = nums[k];
+ nums[k] = temp;
+ }
+}
+
+/* 실행 코드 */
+int main() {
+ int nums[] = {4, 1, 3, 1, 5, 2};
+ int n = sizeof(nums) / sizeof(nums[0]);
+
+ selectionSort(nums, n);
+
+ printf("선택 정렬을 마친 후 nums = ");
+ printArray(nums, n);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_stack_and_queue/CMakeLists.txt b/ko/codes/c/chapter_stack_and_queue/CMakeLists.txt
new file mode 100644
index 000000000..ed3ba840c
--- /dev/null
+++ b/ko/codes/c/chapter_stack_and_queue/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_executable(array_stack array_stack.c)
+add_executable(linkedlist_stack linkedlist_stack.c)
+add_executable(array_queue array_queue.c)
+add_executable(linkedlist_queue linkedlist_queue.c)
+add_executable(array_deque array_deque.c)
+add_executable(linkedlist_deque linkedlist_deque.c)
diff --git a/ko/codes/c/chapter_stack_and_queue/array_deque.c b/ko/codes/c/chapter_stack_and_queue/array_deque.c
new file mode 100644
index 000000000..13f00553f
--- /dev/null
+++ b/ko/codes/c/chapter_stack_and_queue/array_deque.c
@@ -0,0 +1,172 @@
+/**
+ * File: array_deque.c
+ * Created Time: 2023-03-13
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 원형 배열로 구현한 덱 */
+typedef struct {
+ int *nums; // 큐의 원소를 저장하는 배열
+ int front; // 맨 앞 원소를 가리키는 맨 앞 포인터
+ int queSize; // 맨 뒤 포인터, 큐의 맨 뒤 + 1을 가리킨다
+ int queCapacity; // 큐 용량
+} ArrayDeque;
+
+/* 생성자 */
+ArrayDeque *newArrayDeque(int capacity) {
+ ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque));
+ // 배열 초기화
+ deque->queCapacity = capacity;
+ deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity);
+ deque->front = deque->queSize = 0;
+ return deque;
+}
+
+/* 소멸자 */
+void delArrayDeque(ArrayDeque *deque) {
+ free(deque->nums);
+ free(deque);
+}
+
+/* 덱의 용량 반환 */
+int capacity(ArrayDeque *deque) {
+ return deque->queCapacity;
+}
+
+/* 덱의 길이 반환 */
+int size(ArrayDeque *deque) {
+ return deque->queSize;
+}
+
+/* 덱이 비어 있는지 판단 */
+bool empty(ArrayDeque *deque) {
+ return deque->queSize == 0;
+}
+
+/* 원형 배열 인덱스 계산 */
+int dequeIndex(ArrayDeque *deque, int i) {
+ // 나머지 연산을 통해 배열의 처음과 끝을 연결한다
+ // i가 배열의 끝을 넘어가면 처음으로 돌아온다
+ // i가 배열의 처음을 넘어가면 끝으로 돌아온다
+ return ((i + capacity(deque)) % capacity(deque));
+}
+
+/* 맨 앞에 삽입 */
+void pushFirst(ArrayDeque *deque, int num) {
+ if (deque->queSize == capacity(deque)) {
+ printf("덱이 가득 찼다\r\n");
+ return;
+ }
+ // 맨 앞 포인터를 왼쪽으로 한 칸 이동한다
+ // 나머지 연산으로 front가 배열의 처음을 넘어가면 끝으로 돌아가게 한다
+ deque->front = dequeIndex(deque, deque->front - 1);
+ // num을 큐의 맨 앞에 추가한다
+ deque->nums[deque->front] = num;
+ deque->queSize++;
+}
+
+/* 맨 뒤에 삽입 */
+void pushLast(ArrayDeque *deque, int num) {
+ if (deque->queSize == capacity(deque)) {
+ printf("덱이 가득 찼다\r\n");
+ return;
+ }
+ // 맨 뒤 인덱스 + 1을 가리키는 맨 뒤 포인터를 계산한다
+ int rear = dequeIndex(deque, deque->front + deque->queSize);
+ // num을 맨 뒤에 추가한다
+ deque->nums[rear] = num;
+ deque->queSize++;
+}
+
+/* 맨 앞 원소 접근 */
+int peekFirst(ArrayDeque *deque) {
+ // 접근 예외: 덱이 비어 있다
+ assert(empty(deque) == 0);
+ return deque->nums[deque->front];
+}
+
+/* 맨 뒤 원소 접근 */
+int peekLast(ArrayDeque *deque) {
+ // 접근 예외: 덱이 비어 있다
+ assert(empty(deque) == 0);
+ int last = dequeIndex(deque, deque->front + deque->queSize - 1);
+ return deque->nums[last];
+}
+
+/* 맨 앞에서 꺼내기 */
+int popFirst(ArrayDeque *deque) {
+ int num = peekFirst(deque);
+ // 맨 앞 포인터를 뒤로 한 칸 이동한다
+ deque->front = dequeIndex(deque, deque->front + 1);
+ deque->queSize--;
+ return num;
+}
+
+/* 맨 뒤에서 꺼내기 */
+int popLast(ArrayDeque *deque) {
+ int num = peekLast(deque);
+ deque->queSize--;
+ return num;
+}
+
+/* 출력에 사용할 배열 반환 */
+int *toArray(ArrayDeque *deque, int *queSize) {
+ *queSize = deque->queSize;
+ int *res = (int *)calloc(deque->queSize, sizeof(int));
+ int j = deque->front;
+ for (int i = 0; i < deque->queSize; i++) {
+ res[i] = deque->nums[j % deque->queCapacity];
+ j++;
+ }
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ /* 큐 초기화 */
+ int capacity = 10;
+ int queSize;
+ ArrayDeque *deque = newArrayDeque(capacity);
+ pushLast(deque, 3);
+ pushLast(deque, 2);
+ pushLast(deque, 5);
+ printf("덱 deque = ");
+ printArray(toArray(deque, &queSize), queSize);
+
+ /* 원소 접근 */
+ int peekFirstNum = peekFirst(deque);
+ printf("맨 앞 원소 peekFirst = %d\r\n", peekFirstNum);
+ int peekLastNum = peekLast(deque);
+ printf("맨 뒤 원소 peekLast = %d\r\n", peekLastNum);
+
+ /* 큐에 원소 삽입 */
+ pushLast(deque, 4);
+ printf("원소 4를 맨 뒤에 삽입한 후 deque = ");
+ printArray(toArray(deque, &queSize), queSize);
+ pushFirst(deque, 1);
+ printf("원소 1을 맨 앞에 삽입한 후 deque = ");
+ printArray(toArray(deque, &queSize), queSize);
+
+ /* 큐에서 원소 꺼내기 */
+ int popLastNum = popLast(deque);
+ printf("맨 뒤에서 꺼낸 원소 = %d, 꺼낸 후 deque= ", popLastNum);
+ printArray(toArray(deque, &queSize), queSize);
+ int popFirstNum = popFirst(deque);
+ printf("맨 앞에서 꺼낸 원소 = %d, 꺼낸 후 deque= ", popFirstNum);
+ printArray(toArray(deque, &queSize), queSize);
+
+ /* 큐의 길이 반환 */
+ int dequeSize = size(deque);
+ printf("덱의 길이 size = %d\r\n", dequeSize);
+
+ /* 큐가 비어 있는지 판단 */
+ bool isEmpty = empty(deque);
+ printf("큐가 비어 있는가 = %s\r\n", isEmpty ? "true" : "false");
+
+ // 메모리 해제
+ delArrayDeque(deque);
+
+ return 0;
+}
\ No newline at end of file
diff --git a/ko/codes/c/chapter_stack_and_queue/array_queue.c b/ko/codes/c/chapter_stack_and_queue/array_queue.c
new file mode 100644
index 000000000..168360099
--- /dev/null
+++ b/ko/codes/c/chapter_stack_and_queue/array_queue.c
@@ -0,0 +1,134 @@
+/**
+ * File: array_queue.c
+ * Created Time: 2023-01-28
+ * Author: Zero (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 원형 배열로 구현한 큐 */
+typedef struct {
+ int *nums; // 큐의 원소를 저장하는 배열
+ int front; // 맨 앞 원소를 가리키는 맨 앞 포인터
+ int queSize; // 현재 큐의 원소 개수
+ int queCapacity; // 큐 용량
+} ArrayQueue;
+
+/* 생성자 */
+ArrayQueue *newArrayQueue(int capacity) {
+ ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue));
+ // 배열 초기화
+ queue->queCapacity = capacity;
+ queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity);
+ queue->front = queue->queSize = 0;
+ return queue;
+}
+
+/* 소멸자 */
+void delArrayQueue(ArrayQueue *queue) {
+ free(queue->nums);
+ free(queue);
+}
+
+/* 큐의 용량 반환 */
+int capacity(ArrayQueue *queue) {
+ return queue->queCapacity;
+}
+
+/* 큐의 길이 반환 */
+int size(ArrayQueue *queue) {
+ return queue->queSize;
+}
+
+/* 큐가 비어 있는지 판단 */
+bool empty(ArrayQueue *queue) {
+ return queue->queSize == 0;
+}
+
+/* 맨 앞 원소 접근 */
+int peek(ArrayQueue *queue) {
+ assert(size(queue) != 0);
+ return queue->nums[queue->front];
+}
+
+/* 큐에 삽입 */
+void push(ArrayQueue *queue, int num) {
+ if (size(queue) == capacity(queue)) {
+ printf("큐가 가득 찼다\r\n");
+ return;
+ }
+ // 맨 뒤 인덱스 + 1을 가리키는 맨 뒤 포인터를 계산한다
+ // 나머지 연산을 통해 rear가 배열의 끝을 넘어가면 처음으로 돌아오게 한다
+ int rear = (queue->front + queue->queSize) % queue->queCapacity;
+ // num을 맨 뒤에 추가한다
+ queue->nums[rear] = num;
+ queue->queSize++;
+}
+
+/* 큐에서 꺼내기 */
+int pop(ArrayQueue *queue) {
+ int num = peek(queue);
+ // 맨 앞 포인터를 뒤로 한 칸 이동하고, 끝을 넘어가면 배열의 처음으로 돌아온다
+ queue->front = (queue->front + 1) % queue->queCapacity;
+ queue->queSize--;
+ return num;
+}
+
+/* 출력에 사용할 배열 반환 */
+int *toArray(ArrayQueue *queue, int *queSize) {
+ *queSize = queue->queSize;
+ int *res = (int *)calloc(queue->queSize, sizeof(int));
+ int j = queue->front;
+ for (int i = 0; i < queue->queSize; i++) {
+ res[i] = queue->nums[j % queue->queCapacity];
+ j++;
+ }
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ /* 큐 초기화 */
+ int capacity = 10;
+ int queSize;
+ ArrayQueue *queue = newArrayQueue(capacity);
+
+ /* 큐에 원소 삽입 */
+ push(queue, 1);
+ push(queue, 3);
+ push(queue, 2);
+ push(queue, 5);
+ push(queue, 4);
+ printf("큐 queue = ");
+ printArray(toArray(queue, &queSize), queSize);
+
+ /* 맨 앞 원소 접근 */
+ int peekNum = peek(queue);
+ printf("큐의 맨 앞 원소 peek = %d\r\n", peekNum);
+
+ /* 큐에서 원소 꺼내기 */
+ peekNum = pop(queue);
+ printf("큐에서 꺼낸 원소 pop = %d, 꺼낸 후 queue = ", peekNum);
+ printArray(toArray(queue, &queSize), queSize);
+
+ /* 큐의 길이 반환 */
+ int queueSize = size(queue);
+ printf("큐의 길이 size = %d\r\n", queueSize);
+
+ /* 큐가 비어 있는지 판단 */
+ bool isEmpty = empty(queue);
+ printf("큐가 비어 있는가 = %s\r\n", isEmpty ? "true" : "false");
+
+ /* 원형 배열 테스트 */
+ for (int i = 0; i < 10; i++) {
+ push(queue, i);
+ pop(queue);
+ printf("%d번째 삽입 + 꺼내기 후 queue = ", i);
+ printArray(toArray(queue, &queSize), queSize);
+ }
+
+ // 메모리 해제
+ delArrayQueue(queue);
+
+ return 0;
+}
\ No newline at end of file
diff --git a/ko/codes/c/chapter_stack_and_queue/array_stack.c b/ko/codes/c/chapter_stack_and_queue/array_stack.c
new file mode 100644
index 000000000..bc9c02529
--- /dev/null
+++ b/ko/codes/c/chapter_stack_and_queue/array_stack.c
@@ -0,0 +1,103 @@
+/**
+ * File: array_stack.c
+ * Created Time: 2023-01-12
+ * Author: Zero (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+#define MAX_SIZE 5000
+
+/* 배열로 구현한 스택 */
+typedef struct {
+ int *data;
+ int size;
+} ArrayStack;
+
+/* 생성자 */
+ArrayStack *newArrayStack() {
+ ArrayStack *stack = malloc(sizeof(ArrayStack));
+ // 용량 확장을 피하도록 큰 용량으로 초기화한다
+ stack->data = malloc(sizeof(int) * MAX_SIZE);
+ stack->size = 0;
+ return stack;
+}
+
+/* 소멸자 */
+void delArrayStack(ArrayStack *stack) {
+ free(stack->data);
+ free(stack);
+}
+
+/* 스택의 길이 반환 */
+int size(ArrayStack *stack) {
+ return stack->size;
+}
+
+/* 스택이 비어 있는지 판단 */
+bool isEmpty(ArrayStack *stack) {
+ return stack->size == 0;
+}
+
+/* 스택에 삽입 */
+void push(ArrayStack *stack, int num) {
+ if (stack->size == MAX_SIZE) {
+ printf("스택이 가득 찼다\n");
+ return;
+ }
+ stack->data[stack->size] = num;
+ stack->size++;
+}
+
+/* 스택 맨 위 원소 접근 */
+int peek(ArrayStack *stack) {
+ if (stack->size == 0) {
+ printf("스택이 비어 있다\n");
+ return INT_MAX;
+ }
+ return stack->data[stack->size - 1];
+}
+
+/* 스택에서 꺼내기 */
+int pop(ArrayStack *stack) {
+ int val = peek(stack);
+ stack->size--;
+ return val;
+}
+
+/* 실행 코드 */
+int main() {
+ /* 스택 초기화 */
+ ArrayStack *stack = newArrayStack();
+
+ /* 스택에 원소 삽입 */
+ push(stack, 1);
+ push(stack, 3);
+ push(stack, 2);
+ push(stack, 5);
+ push(stack, 4);
+ printf("스택 stack = ");
+ printArray(stack->data, stack->size);
+
+ /* 스택 맨 위 원소 접근 */
+ int val = peek(stack);
+ printf("스택의 맨 위 원소 top = %d\n", val);
+
+ /* 스택에서 원소 꺼내기 */
+ val = pop(stack);
+ printf("스택에서 꺼낸 원소 pop = %d, 꺼낸 후 stack = ", val);
+ printArray(stack->data, stack->size);
+
+ /* 스택의 길이 반환 */
+ int size = stack->size;
+ printf("스택의 길이 size = %d\n", size);
+
+ /* 비어 있는지 판단 */
+ bool empty = isEmpty(stack);
+ printf("스택이 비어 있는가 = %s\n", empty ? "true" : "false");
+
+ // 메모리 해제
+ delArrayStack(stack);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_stack_and_queue/linkedlist_deque.c b/ko/codes/c/chapter_stack_and_queue/linkedlist_deque.c
new file mode 100644
index 000000000..a119c6ff3
--- /dev/null
+++ b/ko/codes/c/chapter_stack_and_queue/linkedlist_deque.c
@@ -0,0 +1,215 @@
+/**
+ * File: linkedlist_deque.c
+ * Created Time: 2023-03-13
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 이중 연결 리스트 노드 */
+typedef struct DoublyListNode {
+ int val; // 노드 값
+ struct DoublyListNode *next; // 다음 노드
+ struct DoublyListNode *prev; // 이전 노드
+} DoublyListNode;
+
+/* 생성자 */
+DoublyListNode *newDoublyListNode(int num) {
+ DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode));
+ new->val = num;
+ new->next = NULL;
+ new->prev = NULL;
+ return new;
+}
+
+/* 소멸자 */
+void delDoublyListNode(DoublyListNode *node) {
+ free(node);
+}
+
+/* 이중 연결 리스트로 구현한 덱 */
+typedef struct {
+ DoublyListNode *front, *rear; // 머리 노드 front, 꼬리 노드 rear
+ int queSize; // 덱의 길이
+} LinkedListDeque;
+
+/* 생성자 */
+LinkedListDeque *newLinkedListDeque() {
+ LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque));
+ deque->front = NULL;
+ deque->rear = NULL;
+ deque->queSize = 0;
+ return deque;
+}
+
+/* 소멸자 */
+void delLinkedListdeque(LinkedListDeque *deque) {
+ // 모든 노드를 해제한다
+ for (int i = 0; i < deque->queSize && deque->front != NULL; i++) {
+ DoublyListNode *tmp = deque->front;
+ deque->front = deque->front->next;
+ free(tmp);
+ }
+ // deque 구조체를 해제한다
+ free(deque);
+}
+
+/* 큐의 길이 반환 */
+int size(LinkedListDeque *deque) {
+ return deque->queSize;
+}
+
+/* 큐가 비어 있는지 판단 */
+bool empty(LinkedListDeque *deque) {
+ return (size(deque) == 0);
+}
+
+/* 큐에 삽입 */
+void push(LinkedListDeque *deque, int num, bool isFront) {
+ DoublyListNode *node = newDoublyListNode(num);
+ // 연결 리스트가 비어 있으면 front와 rear가 모두 node를 가리키게 한다
+ if (empty(deque)) {
+ deque->front = deque->rear = node;
+ }
+ // 맨 앞에 삽입하는 연산
+ else if (isFront) {
+ // node를 연결 리스트의 맨 앞에 추가한다
+ deque->front->prev = node;
+ node->next = deque->front;
+ deque->front = node; // 머리 노드를 갱신한다
+ }
+ // 맨 뒤에 삽입하는 연산
+ else {
+ // node를 연결 리스트의 맨 뒤에 추가한다
+ deque->rear->next = node;
+ node->prev = deque->rear;
+ deque->rear = node;
+ }
+ deque->queSize++; // 큐 길이를 갱신한다
+}
+
+/* 맨 앞에 삽입 */
+void pushFirst(LinkedListDeque *deque, int num) {
+ push(deque, num, true);
+}
+
+/* 맨 뒤에 삽입 */
+void pushLast(LinkedListDeque *deque, int num) {
+ push(deque, num, false);
+}
+
+/* 맨 앞 원소 접근 */
+int peekFirst(LinkedListDeque *deque) {
+ assert(size(deque) && deque->front);
+ return deque->front->val;
+}
+
+/* 맨 뒤 원소 접근 */
+int peekLast(LinkedListDeque *deque) {
+ assert(size(deque) && deque->rear);
+ return deque->rear->val;
+}
+
+/* 큐에서 꺼내기 */
+int pop(LinkedListDeque *deque, bool isFront) {
+ if (empty(deque))
+ return -1;
+ int val;
+ // 큐의 길이가 1이면 머리와 꼬리가 같은 노드를 가리킨다
+ if (deque->queSize == 1) {
+ val = deque->front->val;
+ delDoublyListNode(deque->front); // 같은 노드를 가리키므로 하나만 해제하면 된다
+ deque->front = NULL;
+ deque->rear = NULL;
+ }
+ // 맨 앞에서 꺼내는 연산
+ else if (isFront) {
+ val = peekFirst(deque); // 머리 노드의 값을 임시로 저장한다
+ DoublyListNode *fNext = deque->front->next;
+ fNext->prev = NULL;
+ deque->front->next = NULL;
+ delDoublyListNode(deque->front);
+ deque->front = fNext; // 머리 노드를 갱신한다
+ }
+ // 맨 뒤에서 꺼내는 연산
+ else {
+ val = peekLast(deque); // 꼬리 노드의 값을 임시로 저장한다
+ DoublyListNode *rPrev = deque->rear->prev;
+ rPrev->next = NULL;
+ deque->rear->prev = NULL;
+ delDoublyListNode(deque->rear);
+ deque->rear = rPrev; // 꼬리 노드를 갱신한다
+ }
+ deque->queSize--; // 큐 길이를 갱신한다
+ return val;
+}
+
+/* 맨 앞에서 꺼내기 */
+int popFirst(LinkedListDeque *deque) {
+ return pop(deque, true);
+}
+
+/* 맨 뒤에서 꺼내기 */
+int popLast(LinkedListDeque *deque) {
+ return pop(deque, false);
+}
+
+/* 큐 출력 */
+void printLinkedListDeque(LinkedListDeque *deque) {
+ int *arr = malloc(sizeof(int) * deque->queSize);
+ // 연결 리스트의 데이터를 배열에 복사한다
+ int i;
+ DoublyListNode *node;
+ for (i = 0, node = deque->front; i < deque->queSize; i++) {
+ arr[i] = node->val;
+ node = node->next;
+ }
+ printArray(arr, deque->queSize);
+ free(arr);
+}
+
+/* 실행 코드 */
+int main() {
+ /* 덱 초기화 */
+ LinkedListDeque *deque = newLinkedListDeque();
+ pushLast(deque, 3);
+ pushLast(deque, 2);
+ pushLast(deque, 5);
+ printf("덱 deque = ");
+ printLinkedListDeque(deque);
+
+ /* 원소 접근 */
+ int peekFirstNum = peekFirst(deque);
+ printf("맨 앞 원소 peekFirst = %d\r\n", peekFirstNum);
+ int peekLastNum = peekLast(deque);
+ printf("맨 앞 원소 peekLast = %d\r\n", peekLastNum);
+
+ /* 큐에 원소 삽입 */
+ pushLast(deque, 4);
+ printf("원소 4를 맨 뒤에 삽입한 후 deque =");
+ printLinkedListDeque(deque);
+ pushFirst(deque, 1);
+ printf("원소 1을 맨 앞에 삽입한 후 deque =");
+ printLinkedListDeque(deque);
+
+ /* 큐에서 원소 꺼내기 */
+ int popLastNum = popLast(deque);
+ printf("맨 뒤에서 꺼낸 원소 popLast = %d, 꺼낸 후 deque = ", popLastNum);
+ printLinkedListDeque(deque);
+ int popFirstNum = popFirst(deque);
+ printf("맨 앞에서 꺼낸 원소 popFirst = %d, 꺼낸 후 deque = ", popFirstNum);
+ printLinkedListDeque(deque);
+
+ /* 큐의 길이 반환 */
+ int dequeSize = size(deque);
+ printf("덱의 길이 size = %d\r\n", dequeSize);
+
+ /* 큐가 비어 있는지 판단 */
+ bool isEmpty = empty(deque);
+ printf("덱이 비어 있는가 = %s\r\n", isEmpty ? "true" : "false");
+
+ // 메모리 해제
+ delLinkedListdeque(deque);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_stack_and_queue/linkedlist_queue.c b/ko/codes/c/chapter_stack_and_queue/linkedlist_queue.c
new file mode 100644
index 000000000..321ce5112
--- /dev/null
+++ b/ko/codes/c/chapter_stack_and_queue/linkedlist_queue.c
@@ -0,0 +1,128 @@
+/**
+ * File: linkedlist_queue.c
+ * Created Time: 2023-03-13
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 연결 리스트로 구현한 큐 */
+typedef struct {
+ ListNode *front, *rear;
+ int queSize;
+} LinkedListQueue;
+
+/* 생성자 */
+LinkedListQueue *newLinkedListQueue() {
+ LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue));
+ queue->front = NULL;
+ queue->rear = NULL;
+ queue->queSize = 0;
+ return queue;
+}
+
+/* 소멸자 */
+void delLinkedListQueue(LinkedListQueue *queue) {
+ // 모든 노드를 해제한다
+ while (queue->front != NULL) {
+ ListNode *tmp = queue->front;
+ queue->front = queue->front->next;
+ free(tmp);
+ }
+ // queue 구조체를 해제한다
+ free(queue);
+}
+
+/* 큐의 길이 반환 */
+int size(LinkedListQueue *queue) {
+ return queue->queSize;
+}
+
+/* 큐가 비어 있는지 판단 */
+bool empty(LinkedListQueue *queue) {
+ return (size(queue) == 0);
+}
+
+/* 큐에 삽입 */
+void push(LinkedListQueue *queue, int num) {
+ // 꼬리 노드 위치에 node를 추가한다
+ ListNode *node = newListNode(num);
+ // 큐가 비어 있으면 머리와 꼬리 노드가 모두 해당 노드를 가리키게 한다
+ if (queue->front == NULL) {
+ queue->front = node;
+ queue->rear = node;
+ }
+ // 큐가 비어 있지 않으면 해당 노드를 꼬리 노드 뒤에 추가한다
+ else {
+ queue->rear->next = node;
+ queue->rear = node;
+ }
+ queue->queSize++;
+}
+
+/* 맨 앞 원소 접근 */
+int peek(LinkedListQueue *queue) {
+ assert(size(queue) && queue->front);
+ return queue->front->val;
+}
+
+/* 큐에서 꺼내기 */
+int pop(LinkedListQueue *queue) {
+ int num = peek(queue);
+ ListNode *tmp = queue->front;
+ queue->front = queue->front->next;
+ free(tmp);
+ queue->queSize--;
+ return num;
+}
+
+/* 큐 출력 */
+void printLinkedListQueue(LinkedListQueue *queue) {
+ int *arr = malloc(sizeof(int) * queue->queSize);
+ // 연결 리스트의 데이터를 배열에 복사한다
+ int i;
+ ListNode *node;
+ for (i = 0, node = queue->front; i < queue->queSize; i++) {
+ arr[i] = node->val;
+ node = node->next;
+ }
+ printArray(arr, queue->queSize);
+ free(arr);
+}
+
+/* 실행 코드 */
+int main() {
+ /* 큐 초기화 */
+ LinkedListQueue *queue = newLinkedListQueue();
+
+ /* 큐에 원소 삽입 */
+ push(queue, 1);
+ push(queue, 3);
+ push(queue, 2);
+ push(queue, 5);
+ push(queue, 4);
+ printf("큐 queue = ");
+ printLinkedListQueue(queue);
+
+ /* 맨 앞 원소 접근 */
+ int peekNum = peek(queue);
+ printf("큐의 맨 앞 원소 peek = %d\r\n", peekNum);
+
+ /* 큐에서 원소 꺼내기 */
+ peekNum = pop(queue);
+ printf("큐에서 꺼낸 원소 pop = %d, 꺼낸 후 queue = ", peekNum);
+ printLinkedListQueue(queue);
+
+ /* 큐의 길이 반환 */
+ int queueSize = size(queue);
+ printf("큐의 길이 size = %d\r\n", queueSize);
+
+ /* 큐가 비어 있는지 판단 */
+ bool isEmpty = empty(queue);
+ printf("큐가 비어 있는가 = %s\r\n", isEmpty ? "true" : "false");
+
+ // 메모리 해제
+ delLinkedListQueue(queue);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_stack_and_queue/linkedlist_stack.c b/ko/codes/c/chapter_stack_and_queue/linkedlist_stack.c
new file mode 100644
index 000000000..adfbd5206
--- /dev/null
+++ b/ko/codes/c/chapter_stack_and_queue/linkedlist_stack.c
@@ -0,0 +1,107 @@
+/**
+ * File: linkedlist_stack.c
+ * Created Time: 2023-01-12
+ * Author: Zero (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 연결 리스트로 구현한 스택 */
+typedef struct {
+ ListNode *top; // 머리 노드를 스택의 맨 위로 사용한다
+ int size; // 스택의 길이
+} LinkedListStack;
+
+/* 생성자 */
+LinkedListStack *newLinkedListStack() {
+ LinkedListStack *s = malloc(sizeof(LinkedListStack));
+ s->top = NULL;
+ s->size = 0;
+ return s;
+}
+
+/* 소멸자 */
+void delLinkedListStack(LinkedListStack *s) {
+ while (s->top) {
+ ListNode *n = s->top->next;
+ free(s->top);
+ s->top = n;
+ }
+ free(s);
+}
+
+/* 스택의 길이 반환 */
+int size(LinkedListStack *s) {
+ return s->size;
+}
+
+/* 스택이 비어 있는지 판단 */
+bool isEmpty(LinkedListStack *s) {
+ return size(s) == 0;
+}
+
+/* 스택에 삽입 */
+void push(LinkedListStack *s, int num) {
+ ListNode *node = (ListNode *)malloc(sizeof(ListNode));
+ node->next = s->top; // 새로 추가한 노드의 포인터 필드를 갱신한다
+ node->val = num; // 새로 추가한 노드의 데이터 필드를 갱신한다
+ s->top = node; // 스택의 맨 위를 갱신한다
+ s->size++; // 스택 크기를 갱신한다
+}
+
+/* 스택 맨 위 원소 접근 */
+int peek(LinkedListStack *s) {
+ if (s->size == 0) {
+ printf("스택이 비어 있다\n");
+ return INT_MAX;
+ }
+ return s->top->val;
+}
+
+/* 스택에서 꺼내기 */
+int pop(LinkedListStack *s) {
+ int val = peek(s);
+ ListNode *tmp = s->top;
+ s->top = s->top->next;
+ // 메모리 해제
+ free(tmp);
+ s->size--;
+ return val;
+}
+
+/* 실행 코드 */
+int main() {
+ /* 스택 초기화 */
+ LinkedListStack *stack = newLinkedListStack();
+
+ /* 스택에 원소 삽입 */
+ push(stack, 1);
+ push(stack, 3);
+ push(stack, 2);
+ push(stack, 5);
+ push(stack, 4);
+
+ printf("스택 stack = ");
+ printLinkedList(stack->top);
+
+ /* 스택 맨 위 원소 접근 */
+ int val = peek(stack);
+ printf("스택의 맨 위 원소 top = %d\r\n", val);
+
+ /* 스택에서 원소 꺼내기 */
+ val = pop(stack);
+ printf("스택에서 꺼낸 원소 pop = %d, 꺼낸 후 stack = ", val);
+ printLinkedList(stack->top);
+
+ /* 스택의 길이 반환 */
+ printf("스택의 길이 size = %d\n", size(stack));
+
+ /* 비어 있는지 판단 */
+ bool empty = isEmpty(stack);
+ printf("스택이 비어 있는가 = %s\n", empty ? "true" : "false");
+
+ // 메모리 해제
+ delLinkedListStack(stack);
+
+ return 0;
+}
diff --git a/ko/codes/c/chapter_tree/CMakeLists.txt b/ko/codes/c/chapter_tree/CMakeLists.txt
new file mode 100644
index 000000000..9b4e825ff
--- /dev/null
+++ b/ko/codes/c/chapter_tree/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_executable(avl_tree avl_tree.c)
+add_executable(binary_tree binary_tree.c)
+add_executable(binary_tree_bfs binary_tree_bfs.c)
+add_executable(binary_tree_dfs binary_tree_dfs.c)
+add_executable(binary_search_tree binary_search_tree.c)
+add_executable(array_binary_tree array_binary_tree.c)
diff --git a/ko/codes/c/chapter_tree/array_binary_tree.c b/ko/codes/c/chapter_tree/array_binary_tree.c
new file mode 100644
index 000000000..02a40c32a
--- /dev/null
+++ b/ko/codes/c/chapter_tree/array_binary_tree.c
@@ -0,0 +1,166 @@
+/**
+ * File: array_binary_tree.c
+ * Created Time: 2023-07-29
+ * Author: Gonglja (glj0@outlook.com)
+ */
+
+#include "../utils/common.h"
+
+/* 배열로 표현한 이진 트리 구조체 */
+typedef struct {
+ int *tree;
+ int size;
+} ArrayBinaryTree;
+
+/* 생성자 */
+ArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) {
+ ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree));
+ abt->tree = malloc(sizeof(int) * arrSize);
+ memcpy(abt->tree, arr, sizeof(int) * arrSize);
+ abt->size = arrSize;
+ return abt;
+}
+
+/* 소멸자 */
+void delArrayBinaryTree(ArrayBinaryTree *abt) {
+ free(abt->tree);
+ free(abt);
+}
+
+/* 리스트 용량 */
+int size(ArrayBinaryTree *abt) {
+ return abt->size;
+}
+
+/* 인덱스가 i인 노드의 값 반환 */
+int val(ArrayBinaryTree *abt, int i) {
+ // 인덱스가 범위를 벗어나면 빈자리를 나타내는 INT_MAX를 반환한다
+ if (i < 0 || i >= size(abt))
+ return INT_MAX;
+ return abt->tree[i];
+}
+
+/* 인덱스가 i인 노드의 왼쪽 자식 노드 인덱스 반환 */
+int left(int i) {
+ return 2 * i + 1;
+}
+
+/* 인덱스가 i인 노드의 오른쪽 자식 노드 인덱스 반환 */
+int right(int i) {
+ return 2 * i + 2;
+}
+
+/* 인덱스가 i인 노드의 부모 노드 인덱스 반환 */
+int parent(int i) {
+ return (i - 1) / 2;
+}
+
+/* 레벨 순서 순회 */
+int *levelOrder(ArrayBinaryTree *abt, int *returnSize) {
+ int *res = (int *)malloc(sizeof(int) * size(abt));
+ int index = 0;
+ // 배열을 직접 순회한다
+ for (int i = 0; i < size(abt); i++) {
+ if (val(abt, i) != INT_MAX)
+ res[index++] = val(abt, i);
+ }
+ *returnSize = index;
+ return res;
+}
+
+/* 깊이 우선 순회 */
+void dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) {
+ // 빈칸이면 반환한다
+ if (val(abt, i) == INT_MAX)
+ return;
+ // 전위 순회
+ if (strcmp(order, "pre") == 0)
+ res[(*index)++] = val(abt, i);
+ dfs(abt, left(i), order, res, index);
+ // 중위 순회
+ if (strcmp(order, "in") == 0)
+ res[(*index)++] = val(abt, i);
+ dfs(abt, right(i), order, res, index);
+ // 후위 순회
+ if (strcmp(order, "post") == 0)
+ res[(*index)++] = val(abt, i);
+}
+
+/* 전위 순회 */
+int *preOrder(ArrayBinaryTree *abt, int *returnSize) {
+ int *res = (int *)malloc(sizeof(int) * size(abt));
+ int index = 0;
+ dfs(abt, 0, "pre", res, &index);
+ *returnSize = index;
+ return res;
+}
+
+/* 중위 순회 */
+int *inOrder(ArrayBinaryTree *abt, int *returnSize) {
+ int *res = (int *)malloc(sizeof(int) * size(abt));
+ int index = 0;
+ dfs(abt, 0, "in", res, &index);
+ *returnSize = index;
+ return res;
+}
+
+/* 후위 순회 */
+int *postOrder(ArrayBinaryTree *abt, int *returnSize) {
+ int *res = (int *)malloc(sizeof(int) * size(abt));
+ int index = 0;
+ dfs(abt, 0, "post", res, &index);
+ *returnSize = index;
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ // 이진 트리 초기화
+ // INT_MAX로 빈자리 NULL을 나타낸다
+ int arr[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};
+ int arrSize = sizeof(arr) / sizeof(arr[0]);
+ TreeNode *root = arrayToTree(arr, arrSize);
+ printf("\n이진 트리 초기화\n");
+ printf("이진 트리의 배열 표현:\n");
+ printArray(arr, arrSize);
+ printf("이진 트리의 연결 리스트 표현:\n");
+ printTree(root);
+
+ ArrayBinaryTree *abt = newArrayBinaryTree(arr, arrSize);
+
+ // 노드 접근
+ int i = 1;
+ int l = left(i), r = right(i), p = parent(i);
+ printf("\n현재 노드의 인덱스는 %d, 값은 %d이다\n", i, val(abt, i));
+ printf("왼쪽 자식 노드의 인덱스는 %d, 값은 %d이다\n", l, l < arrSize ? val(abt, l) : INT_MAX);
+ printf("오른쪽 자식 노드의 인덱스는 %d, 값은 %d이다\n", r, r < arrSize ? val(abt, r) : INT_MAX);
+ printf("부모 노드의 인덱스는 %d, 값은 %d이다\n", p, p < arrSize ? val(abt, p) : INT_MAX);
+
+ // 트리 순회
+ int returnSize;
+ int *res;
+
+ res = levelOrder(abt, &returnSize);
+ printf("\n레벨 순서 순회: ");
+ printArray(res, returnSize);
+ free(res);
+
+ res = preOrder(abt, &returnSize);
+ printf("전위 순회: ");
+ printArray(res, returnSize);
+ free(res);
+
+ res = inOrder(abt, &returnSize);
+ printf("중위 순회: ");
+ printArray(res, returnSize);
+ free(res);
+
+ res = postOrder(abt, &returnSize);
+ printf("후위 순회: ");
+ printArray(res, returnSize);
+ free(res);
+
+ // 메모리 해제
+ delArrayBinaryTree(abt);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_tree/avl_tree.c b/ko/codes/c/chapter_tree/avl_tree.c
new file mode 100644
index 000000000..1285eb2de
--- /dev/null
+++ b/ko/codes/c/chapter_tree/avl_tree.c
@@ -0,0 +1,259 @@
+/**
+ * File: avl_tree.c
+ * Created Time: 2023-01-15
+ * Author: Reanon (793584285@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* AVL 트리 구조체 */
+typedef struct {
+ TreeNode *root;
+} AVLTree;
+
+/* 생성자 */
+AVLTree *newAVLTree() {
+ AVLTree *tree = (AVLTree *)malloc(sizeof(AVLTree));
+ tree->root = NULL;
+ return tree;
+}
+
+/* 소멸자 */
+void delAVLTree(AVLTree *tree) {
+ freeMemoryTree(tree->root);
+ free(tree);
+}
+
+/* 노드 높이 반환 */
+int height(TreeNode *node) {
+ // 빈 노드의 높이는 -1이고, 리프 노드의 높이는 0이다
+ if (node != NULL) {
+ return node->height;
+ }
+ return -1;
+}
+
+/* 노드 높이를 갱신한다 */
+void updateHeight(TreeNode *node) {
+ int lh = height(node->left);
+ int rh = height(node->right);
+ // 노드 높이는 가장 높은 서브트리의 높이 + 1이다
+ if (lh > rh) {
+ node->height = lh + 1;
+ } else {
+ node->height = rh + 1;
+ }
+}
+
+/* 균형 인수 반환 */
+int balanceFactor(TreeNode *node) {
+ // 빈 노드의 균형 인수는 0이다
+ if (node == NULL) {
+ return 0;
+ }
+ // 노드의 균형 인수 = 왼쪽 서브트리 높이 - 오른쪽 서브트리 높이
+ return height(node->left) - height(node->right);
+}
+
+/* 오른쪽 회전 연산 */
+TreeNode *rightRotate(TreeNode *node) {
+ TreeNode *child, *grandChild;
+ child = node->left;
+ grandChild = child->right;
+ // child를 중심으로 node를 오른쪽으로 회전한다
+ child->right = node;
+ node->left = grandChild;
+ // 노드 높이를 갱신한다
+ updateHeight(node);
+ updateHeight(child);
+ // 회전한 서브트리의 루트 노드를 반환한다
+ return child;
+}
+
+/* 왼쪽 회전 연산 */
+TreeNode *leftRotate(TreeNode *node) {
+ TreeNode *child, *grandChild;
+ child = node->right;
+ grandChild = child->left;
+ // child를 중심으로 node를 왼쪽으로 회전한다
+ child->left = node;
+ node->right = grandChild;
+ // 노드 높이를 갱신한다
+ updateHeight(node);
+ updateHeight(child);
+ // 회전한 서브트리의 루트 노드를 반환한다
+ return child;
+}
+
+/* 회전 연산을 수행하여 해당 서브트리의 균형 복원 */
+TreeNode *rotate(TreeNode *node) {
+ // 노드 node의 균형 인수를 구한다
+ int bf = balanceFactor(node);
+ // 왼쪽으로 치우친 트리
+ if (bf > 1) {
+ if (balanceFactor(node->left) >= 0) {
+ // 오른쪽 회전
+ return rightRotate(node);
+ } else {
+ // 왼쪽으로 회전한 후 오른쪽으로 회전한다
+ node->left = leftRotate(node->left);
+ return rightRotate(node);
+ }
+ }
+ // 오른쪽으로 치우친 트리
+ if (bf < -1) {
+ if (balanceFactor(node->right) <= 0) {
+ // 왼쪽 회전
+ return leftRotate(node);
+ } else {
+ // 오른쪽으로 회전한 후 왼쪽으로 회전한다
+ node->right = rightRotate(node->right);
+ return leftRotate(node);
+ }
+ }
+ // 균형 트리이므로 회전할 필요 없이 바로 반환한다
+ return node;
+}
+
+/* 노드의 재귀적 삽입(보조 함수) */
+TreeNode *insertHelper(TreeNode *node, int val) {
+ if (node == NULL) {
+ return newTreeNode(val);
+ }
+ /* 1. 삽입 위치를 찾아 노드를 삽입한다 */
+ if (val < node->val) {
+ node->left = insertHelper(node->left, val);
+ } else if (val > node->val) {
+ node->right = insertHelper(node->right, val);
+ } else {
+ // 중복 노드는 삽입하지 않고 바로 반환한다
+ return node;
+ }
+ // 노드 높이를 갱신한다
+ updateHeight(node);
+ /* 2. 회전 연산을 수행하여 해당 서브트리의 균형을 복원한다 */
+ node = rotate(node);
+ // 서브트리의 루트 노드를 반환한다
+ return node;
+}
+
+/* 노드 삽입 */
+void insert(AVLTree *tree, int val) {
+ tree->root = insertHelper(tree->root, val);
+}
+
+/* 노드의 재귀적 삭제(보조 함수) */
+TreeNode *removeHelper(TreeNode *node, int val) {
+ TreeNode *child, *grandChild;
+ if (node == NULL) {
+ return NULL;
+ }
+ /* 1. 노드를 찾아 삭제한다 */
+ if (val < node->val) {
+ node->left = removeHelper(node->left, val);
+ } else if (val > node->val) {
+ node->right = removeHelper(node->right, val);
+ } else {
+ if (node->left == NULL || node->right == NULL) {
+ child = node->left;
+ if (node->right != NULL) {
+ child = node->right;
+ }
+ // 자식 노드 수 = 0이면 node를 바로 삭제하고 반환한다
+ if (child == NULL) {
+ return NULL;
+ } else {
+ // 자식 노드 수 = 1이면 node를 바로 삭제한다
+ node = child;
+ }
+ } else {
+ // 자식 노드 수 = 2이면 중위 순회의 다음 노드를 삭제하고, 그 노드로 현재 노드를 대체한다
+ TreeNode *temp = node->right;
+ while (temp->left != NULL) {
+ temp = temp->left;
+ }
+ int tempVal = temp->val;
+ node->right = removeHelper(node->right, temp->val);
+ node->val = tempVal;
+ }
+ }
+ // 노드 높이를 갱신한다
+ updateHeight(node);
+ /* 2. 회전 연산을 수행하여 해당 서브트리의 균형을 복원한다 */
+ node = rotate(node);
+ // 서브트리의 루트 노드를 반환한다
+ return node;
+}
+
+/* 노드 삭제 */
+// stdio.h를 포함했으므로 여기서는 remove라는 이름을 사용할 수 없다
+void removeItem(AVLTree *tree, int val) {
+ TreeNode *root = removeHelper(tree->root, val);
+}
+
+/* 노드 탐색 */
+TreeNode *search(AVLTree *tree, int val) {
+ TreeNode *cur = tree->root;
+ // 반복하여 탐색하다 리프 노드를 지나면 종료한다
+ while (cur != NULL) {
+ if (cur->val < val) {
+ // 목표 노드는 cur의 오른쪽 서브트리에 있다
+ cur = cur->right;
+ } else if (cur->val > val) {
+ // 목표 노드는 cur의 왼쪽 서브트리에 있다
+ cur = cur->left;
+ } else {
+ // 목표 노드를 찾았으므로 반복문을 종료한다
+ break;
+ }
+ }
+ // 목표 노드를 찾았으므로 반복문을 종료한다
+ return cur;
+}
+
+void testInsert(AVLTree *tree, int val) {
+ insert(tree, val);
+ printf("\n노드 %d 삽입 후의 AVL 트리\n", val);
+ printTree(tree->root);
+}
+
+void testRemove(AVLTree *tree, int val) {
+ removeItem(tree, val);
+ printf("\n노드 %d 삭제 후의 AVL 트리\n", val);
+ printTree(tree->root);
+}
+
+/* 실행 코드 */
+int main() {
+ /* 빈 AVL 트리 초기화 */
+ AVLTree *tree = (AVLTree *)newAVLTree();
+ /* 노드 삽입 */
+ // 노드를 삽입한 후 AVL 트리가 어떻게 균형을 유지하는지 살펴본다
+ testInsert(tree, 1);
+ testInsert(tree, 2);
+ testInsert(tree, 3);
+ testInsert(tree, 4);
+ testInsert(tree, 5);
+ testInsert(tree, 8);
+ testInsert(tree, 7);
+ testInsert(tree, 9);
+ testInsert(tree, 10);
+ testInsert(tree, 6);
+
+ /* 중복 노드 삽입 */
+ testInsert(tree, 7);
+
+ /* 노드 삭제 */
+ // 노드를 삭제한 후 AVL 트리가 어떻게 균형을 유지하는지 살펴본다
+ testRemove(tree, 8); // 차수가 0인 노드 삭제
+ testRemove(tree, 5); // 차수가 1인 노드 삭제
+ testRemove(tree, 4); // 차수가 2인 노드 삭제
+
+ /* 노드 조회 */
+ TreeNode *node = search(tree, 7);
+ printf("\n찾은 노드 객체의 노드 값 = %d \n", node->val);
+
+ // 메모리 해제
+ delAVLTree(tree);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_tree/binary_search_tree.c b/ko/codes/c/chapter_tree/binary_search_tree.c
new file mode 100644
index 000000000..7b49de5a1
--- /dev/null
+++ b/ko/codes/c/chapter_tree/binary_search_tree.c
@@ -0,0 +1,171 @@
+/**
+ * File: binary_search_tree.c
+ * Created Time: 2023-01-11
+ * Author: Reanon (793584285@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 이진 탐색 트리 구조체 */
+typedef struct {
+ TreeNode *root;
+} BinarySearchTree;
+
+/* 생성자 */
+BinarySearchTree *newBinarySearchTree() {
+ // 빈 트리를 초기화한다
+ BinarySearchTree *bst = (BinarySearchTree *)malloc(sizeof(BinarySearchTree));
+ bst->root = NULL;
+ return bst;
+}
+
+/* 소멸자 */
+void delBinarySearchTree(BinarySearchTree *bst) {
+ freeMemoryTree(bst->root);
+ free(bst);
+}
+
+/* 이진 트리의 루트 노드 반환 */
+TreeNode *getRoot(BinarySearchTree *bst) {
+ return bst->root;
+}
+
+/* 노드 탐색 */
+TreeNode *search(BinarySearchTree *bst, int num) {
+ TreeNode *cur = bst->root;
+ // 반복하여 탐색하다 리프 노드를 지나면 종료한다
+ while (cur != NULL) {
+ if (cur->val < num) {
+ // 목표 노드는 cur의 오른쪽 서브트리에 있다
+ cur = cur->right;
+ } else if (cur->val > num) {
+ // 목표 노드는 cur의 왼쪽 서브트리에 있다
+ cur = cur->left;
+ } else {
+ // 목표 노드를 찾았으므로 반복문을 종료한다
+ break;
+ }
+ }
+ // 목표 노드를 반환한다
+ return cur;
+}
+
+/* 노드 삽입 */
+void insert(BinarySearchTree *bst, int num) {
+ // 트리가 비어 있으면 루트 노드를 초기화한다
+ if (bst->root == NULL) {
+ bst->root = newTreeNode(num);
+ return;
+ }
+ TreeNode *cur = bst->root, *pre = NULL;
+ // 반복하여 탐색하다 리프 노드를 지나면 종료한다
+ while (cur != NULL) {
+ // 중복 노드를 찾으면 바로 반환한다
+ if (cur->val == num) {
+ return;
+ }
+ pre = cur;
+ if (cur->val < num) {
+ // 삽입 위치는 cur의 오른쪽 서브트리에 있다
+ cur = cur->right;
+ } else {
+ // 삽입 위치는 cur의 왼쪽 서브트리에 있다
+ cur = cur->left;
+ }
+ }
+ // 노드 삽입
+ TreeNode *node = newTreeNode(num);
+ if (pre->val < num) {
+ pre->right = node;
+ } else {
+ pre->left = node;
+ }
+}
+
+/* 노드 삭제 */
+// stdio.h를 포함했으므로 여기서는 remove라는 이름을 사용할 수 없다
+void removeItem(BinarySearchTree *bst, int num) {
+ // 트리가 비어 있으면 바로 반환하여 종료한다
+ if (bst->root == NULL)
+ return;
+ TreeNode *cur = bst->root, *pre = NULL;
+ // 반복하여 탐색하다 리프 노드를 지나면 종료한다
+ while (cur != NULL) {
+ // 삭제할 노드를 찾았으므로 반복문을 종료한다
+ if (cur->val == num)
+ break;
+ pre = cur;
+ if (cur->val < num) {
+ // 삭제할 노드가 root의 오른쪽 서브트리에 있다
+ cur = cur->right;
+ } else {
+ // 삭제할 노드가 root의 왼쪽 서브트리에 있다
+ cur = cur->left;
+ }
+ }
+ // 삭제할 노드가 없으면 바로 반환한다
+ if (cur == NULL)
+ return;
+ // 삭제할 노드에 자식 노드가 있는지 확인한다
+ if (cur->left == NULL || cur->right == NULL) {
+ /* 자식 노드 수 = 0 또는 1 */
+ // 자식 노드 수가 0 / 1이면 child = nullptr / 해당 자식 노드다
+ TreeNode *child = cur->left != NULL ? cur->left : cur->right;
+ // 노드 cur를 삭제한다
+ if (pre->left == cur) {
+ pre->left = child;
+ } else {
+ pre->right = child;
+ }
+ // 메모리 해제
+ free(cur);
+ } else {
+ /* 자식 노드 수 = 2 */
+ // 중위 순회에서 cur의 다음 노드를 구한다
+ TreeNode *tmp = cur->right;
+ while (tmp->left != NULL) {
+ tmp = tmp->left;
+ }
+ int tmpVal = tmp->val;
+ // 노드 tmp를 재귀적으로 삭제한다
+ removeItem(bst, tmp->val);
+ // tmp로 cur를 덮어쓴다
+ cur->val = tmpVal;
+ }
+}
+
+/* 실행 코드 */
+int main() {
+ /* 이진 탐색 트리 초기화 */
+ int nums[] = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15};
+ BinarySearchTree *bst = newBinarySearchTree();
+ for (int i = 0; i < sizeof(nums) / sizeof(int); i++) {
+ insert(bst, nums[i]);
+ }
+ printf("초기화한 이진 트리:\n");
+ printTree(getRoot(bst));
+
+ /* 노드 탐색 */
+ TreeNode *node = search(bst, 7);
+ printf("찾은 노드 객체의 노드 값 = %d\n", node->val);
+
+ /* 노드 삽입 */
+ insert(bst, 16);
+ printf("노드 16 삽입 후 이진 트리:\n");
+ printTree(getRoot(bst));
+
+ /* 노드 삭제 */
+ removeItem(bst, 1);
+ printf("노드 1 삭제 후 이진 트리:\n");
+ printTree(getRoot(bst));
+ removeItem(bst, 2);
+ printf("노드 2 삭제 후 이진 트리:\n");
+ printTree(getRoot(bst));
+ removeItem(bst, 4);
+ printf("노드 4 삭제 후 이진 트리:\n");
+ printTree(getRoot(bst));
+
+ // 메모리 해제
+ delBinarySearchTree(bst);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_tree/binary_tree.c b/ko/codes/c/chapter_tree/binary_tree.c
new file mode 100644
index 000000000..30d39bc3a
--- /dev/null
+++ b/ko/codes/c/chapter_tree/binary_tree.c
@@ -0,0 +1,43 @@
+/**
+ * File: binary_tree.c
+ * Created Time: 2023-01-11
+ * Author: Reanon (793584285@qq.com)
+ */
+
+#include "../utils/common.h"
+
+/* 실행 코드 */
+int main() {
+ /* 이진 트리 초기화 */
+ // 노드 초기화
+ TreeNode *n1 = newTreeNode(1);
+ TreeNode *n2 = newTreeNode(2);
+ TreeNode *n3 = newTreeNode(3);
+ TreeNode *n4 = newTreeNode(4);
+ TreeNode *n5 = newTreeNode(5);
+ // 노드 사이의 참조(포인터)를 설정한다
+ n1->left = n2;
+ n1->right = n3;
+ n2->left = n4;
+ n2->right = n5;
+ printf("이진 트리 초기화\n");
+ printTree(n1);
+
+ /* 노드 삽입과 삭제 */
+ TreeNode *P = newTreeNode(0);
+ // n1 -> n2 사이에 노드 P를 삽입한다
+ n1->left = P;
+ P->left = n2;
+ printf("노드 P를 삽입한 후\n");
+ printTree(n1);
+
+ // 노드 P를 삭제한다
+ n1->left = n2;
+ // 메모리 해제
+ free(P);
+ printf("노드 P를 삭제한 후\n");
+ printTree(n1);
+
+ freeMemoryTree(n1);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_tree/binary_tree_bfs.c b/ko/codes/c/chapter_tree/binary_tree_bfs.c
new file mode 100644
index 000000000..dceaaf28a
--- /dev/null
+++ b/ko/codes/c/chapter_tree/binary_tree_bfs.c
@@ -0,0 +1,73 @@
+/**
+ * File: binary_tree_bfs.c
+ * Created Time: 2023-01-11
+ * Author: Reanon (793584285@qq.com)
+ */
+
+#include "../utils/common.h"
+
+#define MAX_SIZE 100
+
+/* 레벨 순서 순회 */
+int *levelOrder(TreeNode *root, int *size) {
+ /* 보조 큐 */
+ int front, rear;
+ int index, *arr;
+ TreeNode *node;
+ TreeNode **queue;
+
+ /* 보조 큐 */
+ queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE);
+ // 큐 포인터
+ front = 0, rear = 0;
+ // 루트 노드를 넣는다
+ queue[rear++] = root;
+ // 순회 순서를 저장할 리스트를 초기화한다
+ /* 보조 배열 */
+ arr = (int *)malloc(sizeof(int) * MAX_SIZE);
+ // 배열 포인터
+ index = 0;
+ while (front < rear) {
+ // 큐에서 꺼낸다
+ node = queue[front++];
+ // 노드 값을 저장한다
+ arr[index++] = node->val;
+ if (node->left != NULL) {
+ // 왼쪽 자식 노드를 큐에 삽입한다
+ queue[rear++] = node->left;
+ }
+ if (node->right != NULL) {
+ // 오른쪽 자식 노드를 큐에 삽입한다
+ queue[rear++] = node->right;
+ }
+ }
+ // 배열 길이 값을 갱신한다
+ *size = index;
+ arr = realloc(arr, sizeof(int) * (*size));
+
+ // 보조 배열의 메모리 공간을 해제한다
+ free(queue);
+ return arr;
+}
+
+/* 실행 코드 */
+int main() {
+ /* 이진 트리 초기화 */
+ // 여기서는 배열로부터 이진 트리를 바로 생성하는 함수를 사용한다
+ int nums[] = {1, 2, 3, 4, 5, 6, 7};
+ int size = sizeof(nums) / sizeof(int);
+ TreeNode *root = arrayToTree(nums, size);
+ printf("이진 트리 초기화\n");
+ printTree(root);
+
+ /* 레벨 순서 순회 */
+ // 배열의 길이를 전달해야 한다
+ int *arr = levelOrder(root, &size);
+ printf("레벨 순서 순회의 노드 출력 순서 = ");
+ printArray(arr, size);
+
+ // 메모리 해제
+ freeMemoryTree(root);
+ free(arr);
+ return 0;
+}
diff --git a/ko/codes/c/chapter_tree/binary_tree_dfs.c b/ko/codes/c/chapter_tree/binary_tree_dfs.c
new file mode 100644
index 000000000..02f4c891b
--- /dev/null
+++ b/ko/codes/c/chapter_tree/binary_tree_dfs.c
@@ -0,0 +1,75 @@
+/**
+ * File: binary_tree_dfs.c
+ * Created Time: 2023-01-11
+ * Author: Reanon (793584285@qq.com)
+ */
+
+#include "../utils/common.h"
+
+#define MAX_SIZE 100
+
+// 순회 순서를 저장하는 보조 배열
+int arr[MAX_SIZE];
+
+/* 전위 순회 */
+void preOrder(TreeNode *root, int *size) {
+ if (root == NULL)
+ return;
+ // 방문 우선순위: 루트 노드 -> 왼쪽 서브트리 -> 오른쪽 서브트리
+ arr[(*size)++] = root->val;
+ preOrder(root->left, size);
+ preOrder(root->right, size);
+}
+
+/* 중위 순회 */
+void inOrder(TreeNode *root, int *size) {
+ if (root == NULL)
+ return;
+ // 방문 우선순위: 왼쪽 서브트리 -> 루트 노드 -> 오른쪽 서브트리
+ inOrder(root->left, size);
+ arr[(*size)++] = root->val;
+ inOrder(root->right, size);
+}
+
+/* 후위 순회 */
+void postOrder(TreeNode *root, int *size) {
+ if (root == NULL)
+ return;
+ // 방문 우선순위: 왼쪽 서브트리 -> 오른쪽 서브트리 -> 루트 노드
+ postOrder(root->left, size);
+ postOrder(root->right, size);
+ arr[(*size)++] = root->val;
+}
+
+/* 실행 코드 */
+int main() {
+ /* 이진 트리 초기화 */
+ // 여기서는 배열로부터 이진 트리를 바로 생성하는 함수를 사용한다
+ int nums[] = {1, 2, 3, 4, 5, 6, 7};
+ int size = sizeof(nums) / sizeof(int);
+ TreeNode *root = arrayToTree(nums, size);
+ printf("이진 트리 초기화\n");
+ printTree(root);
+
+ /* 전위 순회 */
+ // 보조 배열 초기화
+ size = 0;
+ preOrder(root, &size);
+ printf("전위 순회의 노드 출력 순서 = ");
+ printArray(arr, size);
+
+ /* 중위 순회 */
+ size = 0;
+ inOrder(root, &size);
+ printf("중위 순회의 노드 출력 순서 = ");
+ printArray(arr, size);
+
+ /* 후위 순회 */
+ size = 0;
+ postOrder(root, &size);
+ printf("후위 순회의 노드 출력 순서 = ");
+ printArray(arr, size);
+
+ freeMemoryTree(root);
+ return 0;
+}
diff --git a/ko/codes/c/utils/CMakeLists.txt b/ko/codes/c/utils/CMakeLists.txt
new file mode 100644
index 000000000..c1ece2e38
--- /dev/null
+++ b/ko/codes/c/utils/CMakeLists.txt
@@ -0,0 +1,5 @@
+add_executable(utils
+ common_test.c
+ common.h print_util.h
+ list_node.h tree_node.h
+ uthash.h)
\ No newline at end of file
diff --git a/ko/codes/c/utils/common.h b/ko/codes/c/utils/common.h
new file mode 100644
index 000000000..686c8877b
--- /dev/null
+++ b/ko/codes/c/utils/common.h
@@ -0,0 +1,36 @@
+/**
+ * File: common.h
+ * Created Time: 2022-12-20
+ * Author: MolDuM (moldum@163.com)、Reanon (793584285@qq.com)
+ */
+
+#ifndef COMMON_H
+#define COMMON_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "list_node.h"
+#include "print_util.h"
+#include "tree_node.h"
+#include "vertex.h"
+
+// 해시 테이블 라이브러리
+#include "uthash.h"
+
+#include "vector.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // COMMON_H
diff --git a/ko/codes/c/utils/common_test.c b/ko/codes/c/utils/common_test.c
new file mode 100644
index 000000000..f5689b3b5
--- /dev/null
+++ b/ko/codes/c/utils/common_test.c
@@ -0,0 +1,35 @@
+/**
+ * File: include_test.c
+ * Created Time: 2023-01-10
+ * Author: Reanon (793584285@qq.com)
+ */
+
+#include "common.h"
+
+void testListNode() {
+ int nums[] = {2, 3, 5, 6, 7};
+ int size = sizeof(nums) / sizeof(int);
+ ListNode *head = arrToLinkedList(nums, size);
+ printLinkedList(head);
+}
+
+void testTreeNode() {
+ int nums[] = {1, 2, 3, INT_MAX, 5, 6, INT_MAX};
+ int size = sizeof(nums) / sizeof(int);
+ TreeNode *root = arrayToTree(nums, size);
+
+ // 트리를 출력한다
+ printTree(root);
+
+ // 트리를 배열로 변환한다
+ int *arr = treeToArray(root, &size);
+ printArray(arr, size);
+}
+
+int main(int argc, char *argv[]) {
+ printf("==testListNode==\n");
+ testListNode();
+ printf("==testTreeNode==\n");
+ testTreeNode();
+ return 0;
+}
diff --git a/ko/codes/c/utils/list_node.h b/ko/codes/c/utils/list_node.h
new file mode 100644
index 000000000..23203a5fb
--- /dev/null
+++ b/ko/codes/c/utils/list_node.h
@@ -0,0 +1,59 @@
+/**
+ * File: list_node.h
+ * Created Time: 2023-01-09
+ * Author: Reanon (793584285@qq.com)
+ */
+
+#ifndef LIST_NODE_H
+#define LIST_NODE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* 연결 리스트 노드 구조체 */
+typedef struct ListNode {
+ int val; // 노드 값
+ struct ListNode *next; // 다음 노드를 가리키는 참조
+} ListNode;
+
+/* 생성자: 새 노드를 초기화한다 */
+ListNode *newListNode(int val) {
+ ListNode *node;
+ node = (ListNode *)malloc(sizeof(ListNode));
+ node->val = val;
+ node->next = NULL;
+ return node;
+}
+
+/* 배열을 역직렬화하여 연결 리스트로 변환한다 */
+ListNode *arrToLinkedList(const int *arr, size_t size) {
+ if (size <= 0) {
+ return NULL;
+ }
+
+ ListNode *dummy = newListNode(0);
+ ListNode *node = dummy;
+ for (int i = 0; i < size; i++) {
+ node->next = newListNode(arr[i]);
+ node = node->next;
+ }
+ return dummy->next;
+}
+
+/* 연결 리스트에 할당한 메모리 공간을 해제한다 */
+void freeMemoryLinkedList(ListNode *cur) {
+ // 메모리 해제
+ ListNode *pre;
+ while (cur != NULL) {
+ pre = cur;
+ cur = cur->next;
+ free(pre);
+ }
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // LIST_NODE_H
diff --git a/ko/codes/c/utils/print_util.h b/ko/codes/c/utils/print_util.h
new file mode 100644
index 000000000..575a193b4
--- /dev/null
+++ b/ko/codes/c/utils/print_util.h
@@ -0,0 +1,131 @@
+/**
+ * File: print_util.h
+ * Created Time: 2022-12-21
+ * Author: MolDum (moldum@163.com), Reanon (793584285@qq.com)
+ */
+
+#ifndef PRINT_UTIL_H
+#define PRINT_UTIL_H
+
+#include
+#include
+#include
+
+#include "list_node.h"
+#include "tree_node.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* 배열을 출력한다 */
+void printArray(int arr[], int size) {
+ if (arr == NULL || size == 0) {
+ printf("[]");
+ return;
+ }
+ printf("[");
+ for (int i = 0; i < size - 1; i++) {
+ printf("%d, ", arr[i]);
+ }
+ printf("%d]\n", arr[size - 1]);
+}
+
+/* 배열을 출력한다 */
+void printArrayFloat(float arr[], int size) {
+ if (arr == NULL || size == 0) {
+ printf("[]");
+ return;
+ }
+ printf("[");
+ for (int i = 0; i < size - 1; i++) {
+ printf("%.2f, ", arr[i]);
+ }
+ printf("%.2f]\n", arr[size - 1]);
+}
+
+/* 연결 리스트 출력 */
+void printLinkedList(ListNode *node) {
+ if (node == NULL) {
+ return;
+ }
+ while (node->next != NULL) {
+ printf("%d -> ", node->val);
+ node = node->next;
+ }
+ printf("%d\n", node->val);
+}
+
+typedef struct Trunk {
+ struct Trunk *prev;
+ char *str;
+} Trunk;
+
+Trunk *newTrunk(Trunk *prev, char *str) {
+ Trunk *trunk = (Trunk *)malloc(sizeof(Trunk));
+ trunk->prev = prev;
+ trunk->str = (char *)malloc(strlen(str) + 1);
+ snprintf(trunk->str, strlen(str) + 1, "%s", str);
+ return trunk;
+}
+
+void showTrunks(Trunk *trunk) {
+ if (trunk == NULL) {
+ return;
+ }
+ showTrunks(trunk->prev);
+ printf("%s", trunk->str);
+}
+
+/**
+ * 이진 트리 출력
+ * 이 트리 출력 코드는 TECHIE DELIGHT에서 가져왔다
+ * https://www.techiedelight.com/c-program-print-binary-tree/
+ */
+void printTreeHelper(TreeNode *node, Trunk *prev, bool isRight) {
+ if (node == NULL) {
+ return;
+ }
+ char *prev_str = " ";
+ Trunk *trunk = newTrunk(prev, prev_str);
+ printTreeHelper(node->right, trunk, true);
+ if (prev == NULL) {
+ trunk->str = "———";
+ } else if (isRight) {
+ trunk->str = "/———";
+ prev_str = " |";
+ } else {
+ trunk->str = "\\———";
+ prev->str = prev_str;
+ }
+ showTrunks(trunk);
+ printf("%d\n", node->val);
+
+ if (prev != NULL) {
+ prev->str = prev_str;
+ }
+ trunk->str = " |";
+
+ printTreeHelper(node->left, trunk, false);
+}
+
+/* 이진 트리 출력 */
+void printTree(TreeNode *root) {
+ printTreeHelper(root, NULL, false);
+}
+
+/* 힙 출력 */
+void printHeap(int arr[], int size) {
+ TreeNode *root;
+ printf("힙의 배열 표현:");
+ printArray(arr, size);
+ printf("힙의 트리 표현:\n");
+ root = arrayToTree(arr, size);
+ printTree(root);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PRINT_UTIL_H
diff --git a/ko/codes/c/utils/tree_node.h b/ko/codes/c/utils/tree_node.h
new file mode 100644
index 000000000..e62bebe41
--- /dev/null
+++ b/ko/codes/c/utils/tree_node.h
@@ -0,0 +1,107 @@
+/**
+ * File: tree_node.h
+ * Created Time: 2023-01-09
+ * Author: Reanon (793584285@qq.com)
+ */
+
+#ifndef TREE_NODE_H
+#define TREE_NODE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+
+#define MAX_NODE_SIZE 5000
+
+/* 이진 트리 노드 구조체 */
+typedef struct TreeNode {
+ int val; // 노드 값
+ int height; // 노드 높이
+ struct TreeNode *left; // 왼쪽 자식 노드 포인터
+ struct TreeNode *right; // 오른쪽 자식 노드 포인터
+} TreeNode;
+
+/* 생성자 */
+TreeNode *newTreeNode(int val) {
+ TreeNode *node;
+
+ node = (TreeNode *)malloc(sizeof(TreeNode));
+ node->val = val;
+ node->height = 0;
+ node->left = NULL;
+ node->right = NULL;
+ return node;
+}
+
+// 직렬화 인코딩 규칙은 다음을 참고한다:
+// https://www.hello-algo.com/ko/chapter_tree/array_representation_of_tree/
+// 이진 트리의 배열 표현:
+// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]
+// 이진 트리의 연결 리스트 표현:
+// /——— 15
+// /——— 7
+// /——— 3
+// | \——— 6
+// | \——— 12
+// ——— 1
+// \——— 2
+// | /——— 9
+// \——— 4
+// \——— 8
+
+/* 리스트를 이진 트리로 역직렬화: 재귀 */
+TreeNode *arrayToTreeDFS(int *arr, int size, int i) {
+ if (i < 0 || i >= size || arr[i] == INT_MAX) {
+ return NULL;
+ }
+ TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode));
+ root->val = arr[i];
+ root->left = arrayToTreeDFS(arr, size, 2 * i + 1);
+ root->right = arrayToTreeDFS(arr, size, 2 * i + 2);
+ return root;
+}
+
+/* 리스트를 이진 트리로 역직렬화 */
+TreeNode *arrayToTree(int *arr, int size) {
+ return arrayToTreeDFS(arr, size, 0);
+}
+
+/* 이진 트리를 리스트로 직렬화: 재귀 */
+void treeToArrayDFS(TreeNode *root, int i, int *res, int *size) {
+ if (root == NULL) {
+ return;
+ }
+ while (i >= *size) {
+ res = realloc(res, (*size + 1) * sizeof(int));
+ res[*size] = INT_MAX;
+ (*size)++;
+ }
+ res[i] = root->val;
+ treeToArrayDFS(root->left, 2 * i + 1, res, size);
+ treeToArrayDFS(root->right, 2 * i + 2, res, size);
+}
+
+/* 이진 트리를 리스트로 직렬화 */
+int *treeToArray(TreeNode *root, int *size) {
+ *size = 0;
+ int *res = NULL;
+ treeToArrayDFS(root, 0, res, size);
+ return res;
+}
+
+/* 이진 트리의 메모리를 해제한다 */
+void freeMemoryTree(TreeNode *root) {
+ if (root == NULL)
+ return;
+ freeMemoryTree(root->left);
+ freeMemoryTree(root->right);
+ free(root);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_NODE_H
diff --git a/ko/codes/c/utils/uthash.h b/ko/codes/c/utils/uthash.h
new file mode 100644
index 000000000..7904a99da
--- /dev/null
+++ b/ko/codes/c/utils/uthash.h
@@ -0,0 +1,1140 @@
+/*
+Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UTHASH_H
+#define UTHASH_H
+
+#define UTHASH_VERSION 2.3.0
+
+#include /* memcmp, memset, strlen */
+#include /* ptrdiff_t */
+#include /* exit */
+
+#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT
+/* 이 코드 경로는 하위 호환성을 위해 제공하지만, 향후 제거할 예정이다. */
+#warning "HASH_DEFINE_OWN_STDINT는 더 이상 권장되지 않는다. 대신 HASH_NO_STDINT를 사용한다"
+typedef unsigned int uint32_t;
+typedef unsigned char uint8_t;
+#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT
+#else
+#include /* uint8_t, uint32_t */
+#endif
+
+/* 이 매크로들은 decltype 또는 이전 GNU 확장인 __typeof를 사용한다.
+ decltype은 최신 컴파일러(C++ 소스를 컴파일할 때 VS2010 또는 gcc 4.3 이상)에서만
+ 사용할 수 있으므로, 이 코드는 상황에 맞는 방법을 사용한다.
+ 둘 다 사용할 수 없는 VS2008에서는 형 변환으로 우회한다. */
+#if !defined(DECLTYPE) && !defined(NO_DECLTYPE)
+#if defined(_MSC_VER) /* MS 컴파일러 */
+#if _MSC_VER >= 1600 && defined(__cplusplus) /* C++ 모드의 VS2010 이상 */
+#define DECLTYPE(x) (decltype(x))
+#else /* VS2008 이하(또는 C 모드의 VS2010) */
+#define NO_DECLTYPE
+#endif
+#elif defined(__MCST__) /* Elbrus C 컴파일러 */
+#define DECLTYPE(x) (__typeof(x))
+#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__)
+#define NO_DECLTYPE
+#else /* GNU, Sun 및 기타 컴파일러 */
+#define DECLTYPE(x) (__typeof(x))
+#endif
+#endif
+
+#ifdef NO_DECLTYPE
+#define DECLTYPE(x)
+#define DECLTYPE_ASSIGN(dst,src) \
+do { \
+ char **_da_dst = (char**)(&(dst)); \
+ *_da_dst = (char*)(src); \
+} while (0)
+#else
+#define DECLTYPE_ASSIGN(dst,src) \
+do { \
+ (dst) = DECLTYPE(dst)(src); \
+} while (0)
+#endif
+
+#ifndef uthash_malloc
+#define uthash_malloc(sz) malloc(sz) /* malloc 함수 */
+#endif
+#ifndef uthash_free
+#define uthash_free(ptr,sz) free(ptr) /* free 함수 */
+#endif
+#ifndef uthash_bzero
+#define uthash_bzero(a,n) memset(a,'\0',n)
+#endif
+#ifndef uthash_strlen
+#define uthash_strlen(s) strlen(s)
+#endif
+
+#ifndef HASH_FUNCTION
+#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv)
+#endif
+
+#ifndef HASH_KEYCMP
+#define HASH_KEYCMP(a,b,n) memcmp(a,b,n)
+#endif
+
+#ifndef uthash_noexpand_fyi
+#define uthash_noexpand_fyi(tbl) /* 확장 중지 내역을 기록하도록 정의할 수 있다 */
+#endif
+#ifndef uthash_expand_fyi
+#define uthash_expand_fyi(tbl) /* 확장 내역을 기록하도록 정의할 수 있다 */
+#endif
+
+#ifndef HASH_NONFATAL_OOM
+#define HASH_NONFATAL_OOM 0
+#endif
+
+#if HASH_NONFATAL_OOM
+/* malloc 실패에서 복구할 수 있다 */
+
+#ifndef uthash_nonfatal_oom
+#define uthash_nonfatal_oom(obj) do {} while (0) /* 치명적이지 않은 메모리 부족 오류 */
+#endif
+
+#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0)
+#define IF_HASH_NONFATAL_OOM(x) x
+
+#else
+/* malloc 실패 시 메모리가 유실되고 해시 테이블을 사용할 수 없게 된다 */
+
+#ifndef uthash_fatal
+#define uthash_fatal(msg) exit(-1) /* 치명적인 메모리 부족 오류 */
+#endif
+
+#define HASH_RECORD_OOM(oomed) uthash_fatal("메모리 부족")
+#define IF_HASH_NONFATAL_OOM(x)
+
+#endif
+
+/* 초기 버킷 수 */
+#define HASH_INITIAL_NUM_BUCKETS 32U /* 초기 버킷 수 */
+#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* 초기 버킷 수의 밑이 2인 로그 */
+#define HASH_BKT_CAPACITY_THRESH 10U /* 버킷의 원소 수가 이 값에 도달하면 확장한다 */
+
+/* 해시 핸들 주소가 hhp인 원소를 계산한다 */
+#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho)))
+/* 원소 주소 elp에서 해시 핸들을 계산한다 */
+#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho)))
+
+#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \
+do { \
+ struct UT_hash_handle *_hd_hh_item = (itemptrhh); \
+ unsigned _hd_bkt; \
+ HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \
+ (head)->hh.tbl->buckets[_hd_bkt].count++; \
+ _hd_hh_item->hh_next = NULL; \
+ _hd_hh_item->hh_prev = NULL; \
+} while (0)
+
+#define HASH_VALUE(keyptr,keylen,hashv) \
+do { \
+ HASH_FUNCTION(keyptr, keylen, hashv); \
+} while (0)
+
+#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \
+do { \
+ (out) = NULL; \
+ if (head) { \
+ unsigned _hf_bkt; \
+ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \
+ if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \
+ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \
+ } \
+ } \
+} while (0)
+
+#define HASH_FIND(hh,head,keyptr,keylen,out) \
+do { \
+ (out) = NULL; \
+ if (head) { \
+ unsigned _hf_hashv; \
+ HASH_VALUE(keyptr, keylen, _hf_hashv); \
+ HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \
+ } \
+} while (0)
+
+#ifdef HASH_BLOOM
+#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM)
+#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL)
+#define HASH_BLOOM_MAKE(tbl,oomed) \
+do { \
+ (tbl)->bloom_nbits = HASH_BLOOM; \
+ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \
+ if (!(tbl)->bloom_bv) { \
+ HASH_RECORD_OOM(oomed); \
+ } else { \
+ uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \
+ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \
+ } \
+} while (0)
+
+#define HASH_BLOOM_FREE(tbl) \
+do { \
+ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \
+} while (0)
+
+#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U)))
+#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U)))
+
+#define HASH_BLOOM_ADD(tbl,hashv) \
+ HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U)))
+
+#define HASH_BLOOM_TEST(tbl,hashv) \
+ HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U)))
+
+#else
+#define HASH_BLOOM_MAKE(tbl,oomed)
+#define HASH_BLOOM_FREE(tbl)
+#define HASH_BLOOM_ADD(tbl,hashv)
+#define HASH_BLOOM_TEST(tbl,hashv) (1)
+#define HASH_BLOOM_BYTELEN 0U
+#endif
+
+#define HASH_MAKE_TABLE(hh,head,oomed) \
+do { \
+ (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \
+ if (!(head)->hh.tbl) { \
+ HASH_RECORD_OOM(oomed); \
+ } else { \
+ uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \
+ (head)->hh.tbl->tail = &((head)->hh); \
+ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \
+ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \
+ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \
+ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \
+ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \
+ (head)->hh.tbl->signature = HASH_SIGNATURE; \
+ if (!(head)->hh.tbl->buckets) { \
+ HASH_RECORD_OOM(oomed); \
+ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
+ } else { \
+ uthash_bzero((head)->hh.tbl->buckets, \
+ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \
+ HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \
+ IF_HASH_NONFATAL_OOM( \
+ if (oomed) { \
+ uthash_free((head)->hh.tbl->buckets, \
+ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \
+ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
+ } \
+ ) \
+ } \
+ } \
+} while (0)
+
+#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \
+do { \
+ (replaced) = NULL; \
+ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \
+ if (replaced) { \
+ HASH_DELETE(hh, head, replaced); \
+ } \
+ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \
+} while (0)
+
+#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \
+do { \
+ (replaced) = NULL; \
+ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \
+ if (replaced) { \
+ HASH_DELETE(hh, head, replaced); \
+ } \
+ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \
+} while (0)
+
+#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \
+do { \
+ unsigned _hr_hashv; \
+ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \
+ HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \
+} while (0)
+
+#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \
+do { \
+ unsigned _hr_hashv; \
+ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \
+ HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \
+} while (0)
+
+#define HASH_APPEND_LIST(hh, head, add) \
+do { \
+ (add)->hh.next = NULL; \
+ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \
+ (head)->hh.tbl->tail->next = (add); \
+ (head)->hh.tbl->tail = &((add)->hh); \
+} while (0)
+
+#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \
+do { \
+ do { \
+ if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \
+ break; \
+ } \
+ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \
+} while (0)
+
+#ifdef NO_DECLTYPE
+#undef HASH_AKBI_INNER_LOOP
+#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \
+do { \
+ char *_hs_saved_head = (char*)(head); \
+ do { \
+ DECLTYPE_ASSIGN(head, _hs_iter); \
+ if (cmpfcn(head, add) > 0) { \
+ DECLTYPE_ASSIGN(head, _hs_saved_head); \
+ break; \
+ } \
+ DECLTYPE_ASSIGN(head, _hs_saved_head); \
+ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \
+} while (0)
+#endif
+
+#if HASH_NONFATAL_OOM
+
+#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \
+do { \
+ if (!(oomed)) { \
+ unsigned _ha_bkt; \
+ (head)->hh.tbl->num_items++; \
+ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \
+ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \
+ if (oomed) { \
+ HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \
+ HASH_DELETE_HH(hh, head, &(add)->hh); \
+ (add)->hh.tbl = NULL; \
+ uthash_nonfatal_oom(add); \
+ } else { \
+ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \
+ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \
+ } \
+ } else { \
+ (add)->hh.tbl = NULL; \
+ uthash_nonfatal_oom(add); \
+ } \
+} while (0)
+
+#else
+
+#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \
+do { \
+ unsigned _ha_bkt; \
+ (head)->hh.tbl->num_items++; \
+ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \
+ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \
+ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \
+ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \
+} while (0)
+
+#endif
+
+
+#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \
+do { \
+ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \
+ (add)->hh.hashv = (hashval); \
+ (add)->hh.key = (char*) (keyptr); \
+ (add)->hh.keylen = (unsigned) (keylen_in); \
+ if (!(head)) { \
+ (add)->hh.next = NULL; \
+ (add)->hh.prev = NULL; \
+ HASH_MAKE_TABLE(hh, add, _ha_oomed); \
+ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \
+ (head) = (add); \
+ IF_HASH_NONFATAL_OOM( } ) \
+ } else { \
+ void *_hs_iter = (head); \
+ (add)->hh.tbl = (head)->hh.tbl; \
+ HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \
+ if (_hs_iter) { \
+ (add)->hh.next = _hs_iter; \
+ if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \
+ HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \
+ } else { \
+ (head) = (add); \
+ } \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \
+ } else { \
+ HASH_APPEND_LIST(hh, head, add); \
+ } \
+ } \
+ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \
+ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \
+} while (0)
+
+#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \
+do { \
+ unsigned _hs_hashv; \
+ HASH_VALUE(keyptr, keylen_in, _hs_hashv); \
+ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \
+} while (0)
+
+#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \
+ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn)
+
+#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \
+ HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn)
+
+#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \
+do { \
+ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \
+ (add)->hh.hashv = (hashval); \
+ (add)->hh.key = (const void*) (keyptr); \
+ (add)->hh.keylen = (unsigned) (keylen_in); \
+ if (!(head)) { \
+ (add)->hh.next = NULL; \
+ (add)->hh.prev = NULL; \
+ HASH_MAKE_TABLE(hh, add, _ha_oomed); \
+ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \
+ (head) = (add); \
+ IF_HASH_NONFATAL_OOM( } ) \
+ } else { \
+ (add)->hh.tbl = (head)->hh.tbl; \
+ HASH_APPEND_LIST(hh, head, add); \
+ } \
+ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \
+ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \
+} while (0)
+
+#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \
+do { \
+ unsigned _ha_hashv; \
+ HASH_VALUE(keyptr, keylen_in, _ha_hashv); \
+ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \
+} while (0)
+
+#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \
+ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add)
+
+#define HASH_ADD(hh,head,fieldname,keylen_in,add) \
+ HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add)
+
+#define HASH_TO_BKT(hashv,num_bkts,bkt) \
+do { \
+ bkt = ((hashv) & ((num_bkts) - 1U)); \
+} while (0)
+
+/* 해시 테이블에서 "delptr"를 삭제한다.
+ * 애플리케이션 순서의 이중 연결 리스트에서 수행하는 "일반적인" 연결 복구 과정이다.
+ * 아래에서 _hd_hh_del을 사용하는 이유는 별도의 설명이 필요하다.
+ * 이전에는 (delptr)를 사용했지만, 다음과 같이
+ * 머리와 삭제 대상에 같은 이름을 사용하면 버그가 발생했다.
+ * HASH_DELETE(hh,users,users);
+ * 이 경우에도 동작해야 하지만, 아래에서 머리(users)를 변경하면
+ * 연결을 복구하는 과정에서 삭제 대상(users)을
+ * 더 이상 참조할 수 없게 된다. 해결 방법은 임시 공간에
+ * 삭제 대상의 포인터를 복사하고, 이후에는 가리키는 대상이 바뀐 (users) 대신
+ * 그 임시 포인터를 통해 참조하는 것이다.
+ */
+#define HASH_DELETE(hh,head,delptr) \
+ HASH_DELETE_HH(hh, head, &(delptr)->hh)
+
+#define HASH_DELETE_HH(hh,head,delptrhh) \
+do { \
+ const struct UT_hash_handle *_hd_hh_del = (delptrhh); \
+ if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \
+ HASH_BLOOM_FREE((head)->hh.tbl); \
+ uthash_free((head)->hh.tbl->buckets, \
+ (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \
+ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
+ (head) = NULL; \
+ } else { \
+ unsigned _hd_bkt; \
+ if (_hd_hh_del == (head)->hh.tbl->tail) { \
+ (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \
+ } \
+ if (_hd_hh_del->prev != NULL) { \
+ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \
+ } else { \
+ DECLTYPE_ASSIGN(head, _hd_hh_del->next); \
+ } \
+ if (_hd_hh_del->next != NULL) { \
+ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \
+ } \
+ HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \
+ HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \
+ (head)->hh.tbl->num_items--; \
+ } \
+ HASH_FSCK(hh, head, "HASH_DELETE_HH"); \
+} while (0)
+
+/* HASH_FIND/HASH_ADD/HASH_DEL의 편의 형식 */
+#define HASH_FIND_STR(head,findstr,out) \
+do { \
+ unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \
+ HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \
+} while (0)
+#define HASH_ADD_STR(head,strfield,add) \
+do { \
+ unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \
+ HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \
+} while (0)
+#define HASH_REPLACE_STR(head,strfield,add,replaced) \
+do { \
+ unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \
+ HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \
+} while (0)
+#define HASH_FIND_INT(head,findint,out) \
+ HASH_FIND(hh,head,findint,sizeof(int),out)
+#define HASH_ADD_INT(head,intfield,add) \
+ HASH_ADD(hh,head,intfield,sizeof(int),add)
+#define HASH_REPLACE_INT(head,intfield,add,replaced) \
+ HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced)
+#define HASH_FIND_PTR(head,findptr,out) \
+ HASH_FIND(hh,head,findptr,sizeof(void *),out)
+#define HASH_ADD_PTR(head,ptrfield,add) \
+ HASH_ADD(hh,head,ptrfield,sizeof(void *),add)
+#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \
+ HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced)
+#define HASH_DEL(head,delptr) \
+ HASH_DELETE(hh,head,delptr)
+
+/* HASH_DEBUG가 정의되어 있으면 HASH_FSCK는 추가/삭제할 때마다 해시 무결성을 검사한다.
+ * uthash 개발자 전용이며, HASH_DEBUG가 정의되지 않으면 컴파일 시 제거된다.
+ */
+#ifdef HASH_DEBUG
+#include /* fprintf, stderr */
+#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0)
+#define HASH_FSCK(hh,head,where) \
+do { \
+ struct UT_hash_handle *_thh; \
+ if (head) { \
+ unsigned _bkt_i; \
+ unsigned _count = 0; \
+ char *_prev; \
+ for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \
+ unsigned _bkt_count = 0; \
+ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \
+ _prev = NULL; \
+ while (_thh) { \
+ if (_prev != (char*)(_thh->hh_prev)) { \
+ HASH_OOPS("%s: 잘못된 hh_prev %p, 실제 값 %p\n", \
+ (where), (void*)_thh->hh_prev, (void*)_prev); \
+ } \
+ _bkt_count++; \
+ _prev = (char*)(_thh); \
+ _thh = _thh->hh_next; \
+ } \
+ _count += _bkt_count; \
+ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \
+ HASH_OOPS("%s: 잘못된 버킷 원소 수 %u, 실제 값 %u\n", \
+ (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \
+ } \
+ } \
+ if (_count != (head)->hh.tbl->num_items) { \
+ HASH_OOPS("%s: 잘못된 hh 원소 수 %u, 실제 값 %u\n", \
+ (where), (head)->hh.tbl->num_items, _count); \
+ } \
+ _count = 0; \
+ _prev = NULL; \
+ _thh = &(head)->hh; \
+ while (_thh) { \
+ _count++; \
+ if (_prev != (char*)_thh->prev) { \
+ HASH_OOPS("%s: 잘못된 prev %p, 실제 값 %p\n", \
+ (where), (void*)_thh->prev, (void*)_prev); \
+ } \
+ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \
+ _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \
+ } \
+ if (_count != (head)->hh.tbl->num_items) { \
+ HASH_OOPS("%s: 잘못된 애플리케이션 원소 수 %u, 실제 값 %u\n", \
+ (where), (head)->hh.tbl->num_items, _count); \
+ } \
+ } \
+} while (0)
+#else
+#define HASH_FSCK(hh,head,where)
+#endif
+
+/* -DHASH_EMIT_KEYS로 컴파일하면 길이가 앞에 붙은 키를
+ * 이 매크로의 값으로 지정한 디스크립터에 출력하여 해시 함수를 조정할 수 있다.
+ * 애플리케이션은 #include 로 write(2)의 함수 원형을 가져올 수 있다. */
+#ifdef HASH_EMIT_KEYS
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \
+do { \
+ unsigned _klen = fieldlen; \
+ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \
+ write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \
+} while (0)
+#else
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)
+#endif
+
+/* Perl v5.6 이전에 사용된 Bernstein 해시 함수이다. (x<<5+x)=x*33임에 유의한다. */
+#define HASH_BER(key,keylen,hashv) \
+do { \
+ unsigned _hb_keylen = (unsigned)keylen; \
+ const unsigned char *_hb_key = (const unsigned char*)(key); \
+ (hashv) = 0; \
+ while (_hb_keylen-- != 0U) { \
+ (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \
+ } \
+} while (0)
+
+
+/* SAX/FNV/OAT/JEN 해시 함수는 다음 주소에 나열된 함수들의 매크로 변형이다
+ * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx
+ * (보관된 링크: https://archive.is/Ivcan )
+ */
+#define HASH_SAX(key,keylen,hashv) \
+do { \
+ unsigned _sx_i; \
+ const unsigned char *_hs_key = (const unsigned char*)(key); \
+ hashv = 0; \
+ for (_sx_i=0; _sx_i < keylen; _sx_i++) { \
+ hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \
+ } \
+} while (0)
+/* FNV-1a 변형 */
+#define HASH_FNV(key,keylen,hashv) \
+do { \
+ unsigned _fn_i; \
+ const unsigned char *_hf_key = (const unsigned char*)(key); \
+ (hashv) = 2166136261U; \
+ for (_fn_i=0; _fn_i < keylen; _fn_i++) { \
+ hashv = hashv ^ _hf_key[_fn_i]; \
+ hashv = hashv * 16777619U; \
+ } \
+} while (0)
+
+#define HASH_OAT(key,keylen,hashv) \
+do { \
+ unsigned _ho_i; \
+ const unsigned char *_ho_key=(const unsigned char*)(key); \
+ hashv = 0; \
+ for(_ho_i=0; _ho_i < keylen; _ho_i++) { \
+ hashv += _ho_key[_ho_i]; \
+ hashv += (hashv << 10); \
+ hashv ^= (hashv >> 6); \
+ } \
+ hashv += (hashv << 3); \
+ hashv ^= (hashv >> 11); \
+ hashv += (hashv << 15); \
+} while (0)
+
+#define HASH_JEN_MIX(a,b,c) \
+do { \
+ a -= b; a -= c; a ^= ( c >> 13 ); \
+ b -= c; b -= a; b ^= ( a << 8 ); \
+ c -= a; c -= b; c ^= ( b >> 13 ); \
+ a -= b; a -= c; a ^= ( c >> 12 ); \
+ b -= c; b -= a; b ^= ( a << 16 ); \
+ c -= a; c -= b; c ^= ( b >> 5 ); \
+ a -= b; a -= c; a ^= ( c >> 3 ); \
+ b -= c; b -= a; b ^= ( a << 10 ); \
+ c -= a; c -= b; c ^= ( b >> 15 ); \
+} while (0)
+
+#define HASH_JEN(key,keylen,hashv) \
+do { \
+ unsigned _hj_i,_hj_j,_hj_k; \
+ unsigned const char *_hj_key=(unsigned const char*)(key); \
+ hashv = 0xfeedbeefu; \
+ _hj_i = _hj_j = 0x9e3779b9u; \
+ _hj_k = (unsigned)(keylen); \
+ while (_hj_k >= 12U) { \
+ _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \
+ + ( (unsigned)_hj_key[2] << 16 ) \
+ + ( (unsigned)_hj_key[3] << 24 ) ); \
+ _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \
+ + ( (unsigned)_hj_key[6] << 16 ) \
+ + ( (unsigned)_hj_key[7] << 24 ) ); \
+ hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \
+ + ( (unsigned)_hj_key[10] << 16 ) \
+ + ( (unsigned)_hj_key[11] << 24 ) ); \
+ \
+ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \
+ \
+ _hj_key += 12; \
+ _hj_k -= 12U; \
+ } \
+ hashv += (unsigned)(keylen); \
+ switch ( _hj_k ) { \
+ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \
+ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \
+ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \
+ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \
+ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \
+ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \
+ case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \
+ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \
+ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \
+ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \
+ case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \
+ default: ; \
+ } \
+ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \
+} while (0)
+
+/* Paul Hsieh 해시 함수 */
+#undef get16bits
+#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \
+ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)
+#define get16bits(d) (*((const uint16_t *) (d)))
+#endif
+
+#if !defined (get16bits)
+#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \
+ +(uint32_t)(((const uint8_t *)(d))[0]) )
+#endif
+#define HASH_SFH(key,keylen,hashv) \
+do { \
+ unsigned const char *_sfh_key=(unsigned const char*)(key); \
+ uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \
+ \
+ unsigned _sfh_rem = _sfh_len & 3U; \
+ _sfh_len >>= 2; \
+ hashv = 0xcafebabeu; \
+ \
+ /* 주 반복문 */ \
+ for (;_sfh_len > 0U; _sfh_len--) { \
+ hashv += get16bits (_sfh_key); \
+ _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \
+ hashv = (hashv << 16) ^ _sfh_tmp; \
+ _sfh_key += 2U*sizeof (uint16_t); \
+ hashv += hashv >> 11; \
+ } \
+ \
+ /* 남은 부분을 처리한다 */ \
+ switch (_sfh_rem) { \
+ case 3: hashv += get16bits (_sfh_key); \
+ hashv ^= hashv << 16; \
+ hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \
+ hashv += hashv >> 11; \
+ break; \
+ case 2: hashv += get16bits (_sfh_key); \
+ hashv ^= hashv << 11; \
+ hashv += hashv >> 17; \
+ break; \
+ case 1: hashv += *_sfh_key; \
+ hashv ^= hashv << 10; \
+ hashv += hashv >> 1; \
+ break; \
+ default: ; \
+ } \
+ \
+ /* 마지막 127비트에 "눈사태 효과"를 강제로 일으킨다 */ \
+ hashv ^= hashv << 3; \
+ hashv += hashv >> 5; \
+ hashv ^= hashv << 4; \
+ hashv += hashv >> 17; \
+ hashv ^= hashv << 25; \
+ hashv += hashv >> 6; \
+} while (0)
+
+/* 지정된 버킷의 원소들을 순회하여 원하는 원소를 찾는다 */
+#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \
+do { \
+ if ((head).hh_head != NULL) { \
+ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \
+ } else { \
+ (out) = NULL; \
+ } \
+ while ((out) != NULL) { \
+ if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \
+ if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \
+ break; \
+ } \
+ } \
+ if ((out)->hh.hh_next != NULL) { \
+ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \
+ } else { \
+ (out) = NULL; \
+ } \
+ } \
+} while (0)
+
+/* 버킷에 원소를 추가한다 */
+#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \
+do { \
+ UT_hash_bucket *_ha_head = &(head); \
+ _ha_head->count++; \
+ (addhh)->hh_next = _ha_head->hh_head; \
+ (addhh)->hh_prev = NULL; \
+ if (_ha_head->hh_head != NULL) { \
+ _ha_head->hh_head->hh_prev = (addhh); \
+ } \
+ _ha_head->hh_head = (addhh); \
+ if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \
+ && !(addhh)->tbl->noexpand) { \
+ HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \
+ IF_HASH_NONFATAL_OOM( \
+ if (oomed) { \
+ HASH_DEL_IN_BKT(head,addhh); \
+ } \
+ ) \
+ } \
+} while (0)
+
+/* 지정된 버킷에서 원소를 삭제한다 */
+#define HASH_DEL_IN_BKT(head,delhh) \
+do { \
+ UT_hash_bucket *_hd_head = &(head); \
+ _hd_head->count--; \
+ if (_hd_head->hh_head == (delhh)) { \
+ _hd_head->hh_head = (delhh)->hh_next; \
+ } \
+ if ((delhh)->hh_prev) { \
+ (delhh)->hh_prev->hh_next = (delhh)->hh_next; \
+ } \
+ if ((delhh)->hh_next) { \
+ (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \
+ } \
+} while (0)
+
+/* 버킷을 확장하면 버킷 수가 두 배가 되고
+ * 원소들이 새 버킷들로 재분배된다. 이상적으로는
+ * 원소들이 새 버킷에 대체로 균등하게 분배된다
+ * (이것이 성립하는 정도는 해당 키 영역에서
+ * 해시 함수의 품질을 평가하는 척도이다).
+ *
+ * 원소들이 더 많은 버킷에 분배되면 각 버킷의 체인 길이
+ * (원소 수)가 줄어든다. 따라서 버킷 확장을 통해
+ * 해시는 체인 길이에 상한을 유지한다. 이렇게 체인 길이에
+ * 상한이 있다는 것이 해시가 상수 시간 탐색을 제공하는 핵심이다.
+ *
+ * 아래에서 tbl->ideal_chain_maxlen을 계산하는 방법에는
+ * 설명이 필요하다. 먼저 이상적인 최대 체인 길이를
+ * *새로운*(두 배로 늘어난) 버킷 수를 기준으로 계산한다는 점에 유의한다.
+ * 분수로 나타내면 n/b이다(n=원소 수, b=새 버킷 수).
+ * 이상적인 체인 길이는 정수이므로 계산하려는 값은
+ * ceil(n/b)이다. 이 해시에서는 부동 소수점 연산에 의존하지 않으므로
+ * 정수로 ceil(n/b)를 계산하려면 다음과 같이 쓸 수 있다
+ *
+ * ceil(n/b) = (n/b) + ((n%b)?1:0)
+ *
+ * 실제로 이 해시의 이전 버전은 바로 이 방법을 사용했다.
+ * 하지만 b가 항상 2의 거듭제곱이라는 점을 이용하여
+ * 조금 개선했다. 밑이 2인 로그를 보관해 두고 이를 lb라고 하면,
+ * 비트 시프트와 논리 AND를 사용하여 다음과 같이 쓸 수 있다:
+ *
+ * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0)
+ *
+ */
+#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \
+do { \
+ unsigned _he_bkt; \
+ unsigned _he_bkt_i; \
+ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \
+ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \
+ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \
+ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \
+ if (!_he_new_buckets) { \
+ HASH_RECORD_OOM(oomed); \
+ } else { \
+ uthash_bzero(_he_new_buckets, \
+ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \
+ (tbl)->ideal_chain_maxlen = \
+ ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \
+ ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \
+ (tbl)->nonideal_items = 0; \
+ for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \
+ _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \
+ while (_he_thh != NULL) { \
+ _he_hh_nxt = _he_thh->hh_next; \
+ HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \
+ _he_newbkt = &(_he_new_buckets[_he_bkt]); \
+ if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \
+ (tbl)->nonideal_items++; \
+ if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \
+ _he_newbkt->expand_mult++; \
+ } \
+ } \
+ _he_thh->hh_prev = NULL; \
+ _he_thh->hh_next = _he_newbkt->hh_head; \
+ if (_he_newbkt->hh_head != NULL) { \
+ _he_newbkt->hh_head->hh_prev = _he_thh; \
+ } \
+ _he_newbkt->hh_head = _he_thh; \
+ _he_thh = _he_hh_nxt; \
+ } \
+ } \
+ uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \
+ (tbl)->num_buckets *= 2U; \
+ (tbl)->log2_num_buckets++; \
+ (tbl)->buckets = _he_new_buckets; \
+ (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \
+ ((tbl)->ineff_expands+1U) : 0U; \
+ if ((tbl)->ineff_expands > 1U) { \
+ (tbl)->noexpand = 1; \
+ uthash_noexpand_fyi(tbl); \
+ } \
+ uthash_expand_fyi(tbl); \
+ } \
+} while (0)
+
+
+/* Simon Tatham의 O(n log(n)) 병합 정렬을 변형한 것이다 */
+/* HASH_SORT는 해시 핸들 이름이 hh라고 가정한다는 점에 유의한다.
+ * HASH_SRT는 해시 핸들 이름을 전달할 수 있도록 추가되었다. */
+#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn)
+#define HASH_SRT(hh,head,cmpfcn) \
+do { \
+ unsigned _hs_i; \
+ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \
+ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \
+ if (head != NULL) { \
+ _hs_insize = 1; \
+ _hs_looping = 1; \
+ _hs_list = &((head)->hh); \
+ while (_hs_looping != 0U) { \
+ _hs_p = _hs_list; \
+ _hs_list = NULL; \
+ _hs_tail = NULL; \
+ _hs_nmerges = 0; \
+ while (_hs_p != NULL) { \
+ _hs_nmerges++; \
+ _hs_q = _hs_p; \
+ _hs_psize = 0; \
+ for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \
+ _hs_psize++; \
+ _hs_q = ((_hs_q->next != NULL) ? \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \
+ if (_hs_q == NULL) { \
+ break; \
+ } \
+ } \
+ _hs_qsize = _hs_insize; \
+ while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \
+ if (_hs_psize == 0U) { \
+ _hs_e = _hs_q; \
+ _hs_q = ((_hs_q->next != NULL) ? \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \
+ _hs_qsize--; \
+ } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \
+ _hs_e = _hs_p; \
+ if (_hs_p != NULL) { \
+ _hs_p = ((_hs_p->next != NULL) ? \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \
+ } \
+ _hs_psize--; \
+ } else if ((cmpfcn( \
+ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \
+ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \
+ )) <= 0) { \
+ _hs_e = _hs_p; \
+ if (_hs_p != NULL) { \
+ _hs_p = ((_hs_p->next != NULL) ? \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \
+ } \
+ _hs_psize--; \
+ } else { \
+ _hs_e = _hs_q; \
+ _hs_q = ((_hs_q->next != NULL) ? \
+ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \
+ _hs_qsize--; \
+ } \
+ if ( _hs_tail != NULL ) { \
+ _hs_tail->next = ((_hs_e != NULL) ? \
+ ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \
+ } else { \
+ _hs_list = _hs_e; \
+ } \
+ if (_hs_e != NULL) { \
+ _hs_e->prev = ((_hs_tail != NULL) ? \
+ ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \
+ } \
+ _hs_tail = _hs_e; \
+ } \
+ _hs_p = _hs_q; \
+ } \
+ if (_hs_tail != NULL) { \
+ _hs_tail->next = NULL; \
+ } \
+ if (_hs_nmerges <= 1U) { \
+ _hs_looping = 0; \
+ (head)->hh.tbl->tail = _hs_tail; \
+ DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \
+ } \
+ _hs_insize *= 2U; \
+ } \
+ HASH_FSCK(hh, head, "HASH_SRT"); \
+ } \
+} while (0)
+
+/* 이 함수는 한 해시에서 원소를 골라 다른 해시에 넣는다.
+ * 결과적으로 선택된 원소들은
+ * 두 해시에 동시에 존재한다. 원소를 복사하는 것은 아니며,
+ * 구조체에 반드시 존재해야 하는 보조 해시
+ * 핸들을 통해 새 해시에 추가한다. */
+#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \
+do { \
+ unsigned _src_bkt, _dst_bkt; \
+ void *_last_elt = NULL, *_elt; \
+ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \
+ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \
+ if ((src) != NULL) { \
+ for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \
+ for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \
+ _src_hh != NULL; \
+ _src_hh = _src_hh->hh_next) { \
+ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \
+ if (cond(_elt)) { \
+ IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \
+ _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \
+ _dst_hh->key = _src_hh->key; \
+ _dst_hh->keylen = _src_hh->keylen; \
+ _dst_hh->hashv = _src_hh->hashv; \
+ _dst_hh->prev = _last_elt; \
+ _dst_hh->next = NULL; \
+ if (_last_elt_hh != NULL) { \
+ _last_elt_hh->next = _elt; \
+ } \
+ if ((dst) == NULL) { \
+ DECLTYPE_ASSIGN(dst, _elt); \
+ HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \
+ IF_HASH_NONFATAL_OOM( \
+ if (_hs_oomed) { \
+ uthash_nonfatal_oom(_elt); \
+ (dst) = NULL; \
+ continue; \
+ } \
+ ) \
+ } else { \
+ _dst_hh->tbl = (dst)->hh_dst.tbl; \
+ } \
+ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \
+ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \
+ (dst)->hh_dst.tbl->num_items++; \
+ IF_HASH_NONFATAL_OOM( \
+ if (_hs_oomed) { \
+ HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \
+ HASH_DELETE_HH(hh_dst, dst, _dst_hh); \
+ _dst_hh->tbl = NULL; \
+ uthash_nonfatal_oom(_elt); \
+ continue; \
+ } \
+ ) \
+ HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \
+ _last_elt = _elt; \
+ _last_elt_hh = _dst_hh; \
+ } \
+ } \
+ } \
+ } \
+ HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \
+} while (0)
+
+#define HASH_CLEAR(hh,head) \
+do { \
+ if ((head) != NULL) { \
+ HASH_BLOOM_FREE((head)->hh.tbl); \
+ uthash_free((head)->hh.tbl->buckets, \
+ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \
+ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
+ (head) = NULL; \
+ } \
+} while (0)
+
+#define HASH_OVERHEAD(hh,head) \
+ (((head) != NULL) ? ( \
+ (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \
+ ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \
+ sizeof(UT_hash_table) + \
+ (HASH_BLOOM_BYTELEN))) : 0U)
+
+#ifdef NO_DECLTYPE
+#define HASH_ITER(hh,head,el,tmp) \
+for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \
+ (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL)))
+#else
+#define HASH_ITER(hh,head,el,tmp) \
+for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \
+ (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL)))
+#endif
+
+/* 해시에 있는 원소 수를 구한다 */
+#define HASH_COUNT(head) HASH_CNT(hh,head)
+#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U)
+
+typedef struct UT_hash_bucket {
+ struct UT_hash_handle *hh_head;
+ unsigned count;
+
+ /* expand_mult는 보통 0으로 설정한다. 이 경우 최대 체인 길이의
+ * 임곗값은 기본값인 HASH_BKT_CAPACITY_THRESH로 적용된다.
+ * (버킷의 체인이 이 길이를 초과하면 버킷 확장이 발생한다.)
+ * 그러나 expand_mult를 0이 아닌 값으로 설정하면 버킷 확장이 지연된다.
+ * 즉, 이 특정 버킷에 원소를 추가하여 발생할 확장을
+ * 체인 길이가 HASH_BKT_CAPACITY_THRESH의 *배수*에 도달할 때까지 미룬다.
+ * (배수는 단순히 expand_mult+1이다.) 이 배수를 사용하는
+ * 목적은 비용이 큰 버킷 확장을 줄이는 것이다.
+ * 특정 버킷이 과도하게 사용되는 경향이 있음을 알 때 유용하다.
+ * O(n)의 버킷 확장을 지나치게 자주 수행하는 것보다
+ * 체인 길이가 더 길어지더라도 상한 안에서 증가하도록 두는 편이 낫다.
+ */
+ unsigned expand_mult;
+
+} UT_hash_bucket;
+
+/* 외부 분석에서 해시 테이블을 찾는 데만 사용하는 임의의 시그니처 */
+#define HASH_SIGNATURE 0xa0111fe1u
+#define HASH_BLOOM_SIGNATURE 0xb12220f2u
+
+typedef struct UT_hash_table {
+ UT_hash_bucket *buckets;
+ unsigned num_buckets, log2_num_buckets;
+ unsigned num_items;
+ struct UT_hash_handle *tail; /* 빠른 끝 추가를 위한 애플리케이션 순서의 마지막 hh */
+ ptrdiff_t hho; /* 해시 핸들의 오프셋(원소 내 해시 핸들의 바이트 위치 */
+
+ /* 이상적인 상황(모든 버킷이 균등하게 사용됨)에서는 어느 버킷도
+ * ceil(#items/#buckets)보다 많은 원소를 갖지 않는다. 이것이 이상적인 체인 길이이다. */
+ unsigned ideal_chain_maxlen;
+
+ /* nonideal_items는 해시에서 체인 내 위치가
+ * 이상적인 최대 체인 길이를 초과하는 원소의 수이다. 이 원소들은 불균등한
+ * 해시 분포의 영향을 받아, 체인을 순회하여 도달하는 데 이상적인 횟수보다 많은 단계가 필요하다 */
+ unsigned nonideal_items;
+
+ /* 비효율적인 확장이란 버킷 수를 두 배로 늘린 뒤에도
+ * 해시 원소의 과반수가 이상적인 범위를 벗어난
+ * 체인 위치에 있는 경우이다. 연속 두 번의 확장에서 이런 일이 발생하면
+ * 추가 확장은 도움이 되지 않으므로 더 이상 확장하지 않는다. 이는 해시
+ * 함수가 키 영역에 잘 맞지 않을 때 발생한다. 확장을 중지해도
+ * 해시는 계속 동작하지만 더 이상 상수 시간을 보장하지 않는다. */
+ unsigned ineff_expands, noexpand;
+
+ uint32_t signature; /* 외부 분석에서 해시 테이블을 찾는 데만 사용한다 */
+#ifdef HASH_BLOOM
+ uint32_t bloom_sig; /* 외부 분석에서 블룸 필터가 있는지 확인하는 데만 사용한다 */
+ uint8_t *bloom_bv;
+ uint8_t bloom_nbits;
+#endif
+
+} UT_hash_table;
+
+typedef struct UT_hash_handle {
+ struct UT_hash_table *tbl;
+ void *prev; /* 애플리케이션 순서의 이전 원소 */
+ void *next; /* 애플리케이션 순서의 다음 원소 */
+ struct UT_hash_handle *hh_prev; /* 버킷 순서의 이전 hh */
+ struct UT_hash_handle *hh_next; /* 버킷 순서의 다음 hh */
+ const void *key; /* 이를 포함하는 구조체의 키를 가리키는 포인터 */
+ unsigned keylen; /* 이를 포함하는 구조체의 키 길이 */
+ unsigned hashv; /* hash-fcn(key)의 결과 */
+} UT_hash_handle;
+
+#endif /* UTHASH_H */
diff --git a/ko/codes/c/utils/vector.h b/ko/codes/c/utils/vector.h
new file mode 100644
index 000000000..d3f99f8ad
--- /dev/null
+++ b/ko/codes/c/utils/vector.h
@@ -0,0 +1,259 @@
+/**
+ * File: vector.h
+ * Created Time: 2023-07-13
+ * Author: Zuoxun (845242523@qq.com)、Gonglja (glj0@outlook.com)
+ */
+
+#ifndef VECTOR_H
+#define VECTOR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* 벡터 타입 정의 */
+typedef struct vector {
+ int size; // 현재 벡터의 크기
+ int capacity; // 현재 벡터의 용량
+ int depth; // 현재 벡터의 깊이
+ void **data; // 데이터를 가리키는 포인터 배열
+} vector;
+
+/* 벡터 생성 */
+vector *newVector() {
+ vector *v = malloc(sizeof(vector));
+ v->size = 0;
+ v->capacity = 4;
+ v->depth = 1;
+ v->data = malloc(v->capacity * sizeof(void *));
+ return v;
+}
+
+/* 크기와 원소 기본값을 지정하여 벡터 생성 */
+vector *_newVector(int size, void *elem, int elemSize) {
+ vector *v = malloc(sizeof(vector));
+ v->size = size;
+ v->capacity = size;
+ v->depth = 1;
+ v->data = malloc(v->capacity * sizeof(void *));
+ for (int i = 0; i < size; i++) {
+ void *tmp = malloc(sizeof(char) * elemSize);
+ memcpy(tmp, elem, elemSize);
+ v->data[i] = tmp;
+ }
+ return v;
+}
+
+/* 벡터 소멸 */
+void delVector(vector *v) {
+ if (v) {
+ if (v->depth == 0) {
+ return;
+ } else if (v->depth == 1) {
+ for (int i = 0; i < v->size; i++) {
+ free(v->data[i]);
+ }
+ free(v);
+ } else {
+ for (int i = 0; i < v->size; i++) {
+ delVector(v->data[i]);
+ }
+ v->depth--;
+ }
+ }
+}
+
+/* 벡터 끝에 원소를 복사하여 추가한다 */
+void vectorPushback(vector *v, void *elem, int elemSize) {
+ if (v->size == v->capacity) {
+ v->capacity *= 2;
+ v->data = realloc(v->data, v->capacity * sizeof(void *));
+ }
+ void *tmp = malloc(sizeof(char) * elemSize);
+ memcpy(tmp, elem, elemSize);
+ v->data[v->size++] = tmp;
+}
+
+/* 벡터 끝에서 원소를 꺼낸다 */
+void vectorPopback(vector *v) {
+ if (v->size != 0) {
+ free(v->data[v->size - 1]);
+ v->size--;
+ }
+}
+
+/* 벡터 비우기 */
+void vectorClear(vector *v) {
+ delVector(v);
+ v->size = 0;
+ v->capacity = 4;
+ v->depth = 1;
+ v->data = malloc(v->capacity * sizeof(void *));
+}
+
+/* 벡터의 크기 반환 */
+int vectorSize(vector *v) {
+ return v->size;
+}
+
+/* 벡터의 마지막 원소 반환 */
+void *vectorBack(vector *v) {
+ int n = v->size;
+ return n > 0 ? v->data[n - 1] : NULL;
+}
+
+/* 벡터의 첫 원소 반환 */
+void *vectorFront(vector *v) {
+ return v->size > 0 ? v->data[0] : NULL;
+}
+
+/* 벡터에서 인덱스가 pos인 원소 반환 */
+void *vectorAt(vector *v, int pos) {
+ if (pos < 0 || pos >= v->size) {
+ printf("vectorAt: 범위를 벗어났다\n");
+ return NULL;
+ }
+ return v->data[pos];
+}
+
+/* 벡터에서 인덱스가 pos인 원소 설정 */
+void vectorSet(vector *v, int pos, void *elem, int elemSize) {
+ if (pos < 0 || pos >= v->size) {
+ printf("vectorSet: 범위를 벗어났다\n");
+ return;
+ }
+ free(v->data[pos]);
+ void *tmp = malloc(sizeof(char) * elemSize);
+ memcpy(tmp, elem, elemSize);
+ v->data[pos] = tmp;
+}
+
+/* 벡터 용량 확장 */
+void vectorExpand(vector *v) {
+ v->capacity *= 2;
+ v->data = realloc(v->data, v->capacity * sizeof(void *));
+}
+
+/* 벡터 용량 축소 */
+void vectorShrink(vector *v) {
+ v->capacity /= 2;
+ v->data = realloc(v->data, v->capacity * sizeof(void *));
+}
+
+/* 벡터의 인덱스 pos에 원소 삽입 */
+void vectorInsert(vector *v, int pos, void *elem, int elemSize) {
+ if (v->size == v->capacity) {
+ vectorExpand(v);
+ }
+ for (int j = v->size; j > pos; j--) {
+ v->data[j] = v->data[j - 1];
+ }
+ void *tmp = malloc(sizeof(char) * elemSize);
+ memcpy(tmp, elem, elemSize);
+ v->data[pos] = tmp;
+ v->size++;
+}
+
+/* 벡터에서 인덱스가 pos인 원소 삭제 */
+void vectorErase(vector *v, int pos) {
+ if (v->size != 0) {
+ free(v->data[pos]);
+ for (int j = pos; j < v->size - 1; j++) {
+ v->data[j] = v->data[j + 1];
+ }
+ v->size--;
+ }
+}
+
+/* 벡터 원소 교환 */
+void vectorSwap(vector *v, int i, int j) {
+ void *tmp = v->data[i];
+ v->data[i] = v->data[j];
+ v->data[j] = tmp;
+}
+
+/* 벡터가 비어 있는지 확인 */
+bool vectorEmpty(vector *v) {
+ return v->size == 0;
+}
+
+/* 벡터가 가득 찼는지 확인 */
+bool vectorFull(vector *v) {
+ return v->size == v->capacity;
+}
+
+/* 두 벡터가 같은지 확인 */
+bool vectorEqual(vector *v1, vector *v2) {
+ if (v1->size != v2->size) {
+ printf("크기가 다르다\n");
+ return false;
+ }
+ for (int i = 0; i < v1->size; i++) {
+ void *a = v1->data[i];
+ void *b = v2->data[i];
+ if (memcmp(a, b, sizeof(a)) != 0) {
+ printf("데이터 %d의 값이 다르다\n", i);
+ return false;
+ }
+ }
+ return true;
+}
+
+/* 벡터 내부 정렬 */
+void vectorSort(vector *v, int (*cmp)(const void *, const void *)) {
+ qsort(v->data, v->size, sizeof(void *), cmp);
+}
+
+/* 출력 함수: 변수를 출력하는 함수를 인수로 전달해야 한다 */
+/* 현재는 깊이가 1인 vector만 출력할 수 있다 */
+void printVector(vector *v, void (*printFunc)(vector *v, void *p)) {
+ if (v) {
+ if (v->depth == 0) {
+ return;
+ } else if (v->depth == 1) {
+ if(v->size == 0) {
+ printf("\n");
+ return;
+ }
+ for (int i = 0; i < v->size; i++) {
+ if (i == 0) {
+ printf("[");
+ } else if (i == v->size - 1) {
+ printFunc(v, v->data[i]);
+ printf("]\r\n");
+ break;
+ }
+ printFunc(v, v->data[i]);
+ printf(",");
+ }
+ } else {
+ for (int i = 0; i < v->size; i++) {
+ printVector(v->data[i], printFunc);
+ }
+ v->depth--;
+ }
+ }
+}
+
+/* 현재는 깊이가 2인 vector만 출력할 수 있다 */
+void printVectorMatrix(vector *vv, void (*printFunc)(vector *v, void *p)) {
+ printf("[\n");
+ for (int i = 0; i < vv->size; i++) {
+ vector *v = (vector *)vv->data[i];
+ printf(" [");
+ for (int j = 0; j < v->size; j++) {
+ printFunc(v, v->data[j]);
+ if (j != v->size - 1)
+ printf(",");
+ }
+ printf("],");
+ printf("\n");
+ }
+ printf("]\n");
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // VECTOR_H
diff --git a/ko/codes/c/utils/vertex.h b/ko/codes/c/utils/vertex.h
new file mode 100644
index 000000000..561ebeba4
--- /dev/null
+++ b/ko/codes/c/utils/vertex.h
@@ -0,0 +1,49 @@
+/**
+ * File: vertex.h
+ * Created Time: 2023-10-28
+ * Author: krahets (krahets@163.com)
+ */
+
+#ifndef VERTEX_H
+#define VERTEX_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* 정점 구조체 */
+typedef struct {
+ int val;
+} Vertex;
+
+/* 생성자: 새 노드를 초기화한다 */
+Vertex *newVertex(int val) {
+ Vertex *vet;
+ vet = (Vertex *)malloc(sizeof(Vertex));
+ vet->val = val;
+ return vet;
+}
+
+/* 값 배열을 정점 배열로 변환한다 */
+Vertex **valsToVets(int *vals, int size) {
+ Vertex **vertices = (Vertex **)malloc(size * sizeof(Vertex *));
+ for (int i = 0; i < size; ++i) {
+ vertices[i] = newVertex(vals[i]);
+ }
+ return vertices;
+}
+
+/* 정점 배열을 값 배열로 변환한다 */
+int *vetsToVals(Vertex **vertices, int size) {
+ int *vals = (int *)malloc(size * sizeof(int));
+ for (int i = 0; i < size; ++i) {
+ vals[i] = vertices[i]->val;
+ }
+ return vals;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // VERTEX_H
diff --git a/ko/codes/cpp/.gitignore b/ko/codes/cpp/.gitignore
new file mode 100644
index 000000000..a13ad878b
--- /dev/null
+++ b/ko/codes/cpp/.gitignore
@@ -0,0 +1,10 @@
+# 모든 항목을 무시한다
+*
+# 확장자가 있는 모든 항목을 무시 대상에서 제외한다
+!*.*
+# 모든 디렉터리를 무시 대상에서 제외한다
+!*/
+
+*.dSYM/
+
+build/
diff --git a/ko/codes/cpp/CMakeLists.txt b/ko/codes/cpp/CMakeLists.txt
new file mode 100644
index 000000000..1e80bc4d7
--- /dev/null
+++ b/ko/codes/cpp/CMakeLists.txt
@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 3.10)
+project(hello_algo CXX)
+
+set(CMAKE_CXX_STANDARD 11)
+
+include_directories(./include)
+
+add_subdirectory(chapter_computational_complexity)
+add_subdirectory(chapter_array_and_linkedlist)
+add_subdirectory(chapter_stack_and_queue)
+add_subdirectory(chapter_hashing)
+add_subdirectory(chapter_tree)
+add_subdirectory(chapter_heap)
+add_subdirectory(chapter_graph)
+add_subdirectory(chapter_searching)
+add_subdirectory(chapter_sorting)
+add_subdirectory(chapter_divide_and_conquer)
+add_subdirectory(chapter_backtracking)
+add_subdirectory(chapter_dynamic_programming)
+add_subdirectory(chapter_greedy)
diff --git a/ko/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt b/ko/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt
new file mode 100644
index 000000000..2e933e016
--- /dev/null
+++ b/ko/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_executable(array array.cpp)
+add_executable(linked_list linked_list.cpp)
+add_executable(list list.cpp)
+add_executable(my_list my_list.cpp)
diff --git a/ko/codes/cpp/chapter_array_and_linkedlist/array.cpp b/ko/codes/cpp/chapter_array_and_linkedlist/array.cpp
new file mode 100644
index 000000000..e910ed2b1
--- /dev/null
+++ b/ko/codes/cpp/chapter_array_and_linkedlist/array.cpp
@@ -0,0 +1,113 @@
+/**
+ * File: array.cpp
+ * Created Time: 2022-11-25
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 원소에 임의 접근 */
+int randomAccess(int *nums, int size) {
+ // 구간 [0, size)에서 무작위로 수 하나 선택
+ int randomIndex = rand() % size;
+ // 무작위 원소를 가져와 반환한다
+ int randomNum = nums[randomIndex];
+ return randomNum;
+}
+
+/* 배열 길이 확장 */
+int *extend(int *nums, int size, int enlarge) {
+ // 길이를 확장한 배열을 초기화한다
+ int *res = new int[size + enlarge];
+ // 원래 배열의 모든 원소를 새 배열에 복사한다
+ for (int i = 0; i < size; i++) {
+ res[i] = nums[i];
+ }
+ // 메모리 해제
+ delete[] nums;
+ // 확장한 새 배열을 반환한다
+ return res;
+}
+
+/* 배열의 인덱스 index에 원소 num 삽입 */
+void insert(int *nums, int size, int num, int index) {
+ // 인덱스 index와 그 뒤에 있는 모든 원소를 한 칸씩 뒤로 이동한다
+ for (int i = size - 1; i > index; i--) {
+ nums[i] = nums[i - 1];
+ }
+ // index 위치의 원소에 num을 대입한다
+ nums[index] = num;
+}
+
+/* 인덱스 index에 있는 원소 삭제 */
+void remove(int *nums, int size, int index) {
+ // 인덱스 index 뒤에 있는 모든 원소를 한 칸씩 앞으로 이동한다
+ for (int i = index; i < size - 1; i++) {
+ nums[i] = nums[i + 1];
+ }
+}
+
+/* 배열 순회 */
+void traverse(int *nums, int size) {
+ int count = 0;
+ // 인덱스를 통해 배열을 순회한다
+ for (int i = 0; i < size; i++) {
+ count += nums[i];
+ }
+}
+
+/* 배열에서 지정한 원소 탐색 */
+int find(int *nums, int size, int target) {
+ for (int i = 0; i < size; i++) {
+ if (nums[i] == target)
+ return i;
+ }
+ return -1;
+}
+
+/* 실행 코드 */
+int main() {
+ /* 배열 초기화 */
+ int size = 5;
+ int *arr = new int[size];
+ cout << "배열 arr = ";
+ printArray(arr, size);
+
+ int *nums = new int[size]{1, 3, 2, 5, 4};
+ cout << "배열 nums = ";
+ printArray(nums, size);
+
+ /* 임의 접근 */
+ int randomNum = randomAccess(nums, size);
+ cout << "nums에서 무작위로 가져온 원소 " << randomNum << endl;
+
+ /* 길이 확장 */
+ int enlarge = 3;
+ nums = extend(nums, size, enlarge);
+ size += enlarge;
+ cout << "배열 길이를 8로 확장한 후 nums = ";
+ printArray(nums, size);
+
+ /* 원소 삽입 */
+ insert(nums, size, 6, 3);
+ cout << "인덱스 3에 숫자 6을 삽입한 후 nums = ";
+ printArray(nums, size);
+
+ /* 원소 삭제 */
+ remove(nums, size, 2);
+ cout << "인덱스 2의 원소를 삭제한 후 nums = ";
+ printArray(nums, size);
+
+ /* 배열 순회 */
+ traverse(nums, size);
+
+ /* 원소 탐색 */
+ int index = find(nums, size, 3);
+ cout << "nums에서 원소 3을 탐색하여 얻은 인덱스 = " << index << endl;
+
+ // 메모리 해제
+ delete[] arr;
+ delete[] nums;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp b/ko/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp
new file mode 100644
index 000000000..26d0159d0
--- /dev/null
+++ b/ko/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp
@@ -0,0 +1,89 @@
+/**
+ * File: linked_list.cpp
+ * Created Time: 2022-11-25
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 연결 리스트의 노드 n0 뒤에 노드 P 삽입 */
+void insert(ListNode *n0, ListNode *P) {
+ ListNode *n1 = n0->next;
+ P->next = n1;
+ n0->next = P;
+}
+
+/* 연결 리스트의 노드 n0 바로 뒤에 있는 노드 삭제 */
+void remove(ListNode *n0) {
+ if (n0->next == nullptr)
+ return;
+ // n0 -> P -> n1
+ ListNode *P = n0->next;
+ ListNode *n1 = P->next;
+ n0->next = n1;
+ // 메모리 해제
+ delete P;
+}
+
+/* 연결 리스트에서 인덱스가 index인 노드에 접근 */
+ListNode *access(ListNode *head, int index) {
+ for (int i = 0; i < index; i++) {
+ if (head == nullptr)
+ return nullptr;
+ head = head->next;
+ }
+ return head;
+}
+
+/* 연결 리스트에서 값이 target인 첫 번째 노드 탐색 */
+int find(ListNode *head, int target) {
+ int index = 0;
+ while (head != nullptr) {
+ if (head->val == target)
+ return index;
+ head = head->next;
+ index++;
+ }
+ return -1;
+}
+
+/* 실행 코드 */
+int main() {
+ /* 연결 리스트 초기화 */
+ // 각 노드를 초기화한다
+ ListNode *n0 = new ListNode(1);
+ ListNode *n1 = new ListNode(3);
+ ListNode *n2 = new ListNode(2);
+ ListNode *n3 = new ListNode(5);
+ ListNode *n4 = new ListNode(4);
+ // 노드 사이의 참조를 설정한다
+ n0->next = n1;
+ n1->next = n2;
+ n2->next = n3;
+ n3->next = n4;
+ cout << "초기화한 연결 리스트" << endl;
+ printLinkedList(n0);
+
+ /* 노드 삽입 */
+ insert(n0, new ListNode(0));
+ cout << "노드를 삽입한 후의 연결 리스트" << endl;
+ printLinkedList(n0);
+
+ /* 노드 삭제 */
+ remove(n0);
+ cout << "노드를 삭제한 후의 연결 리스트" << endl;
+ printLinkedList(n0);
+
+ /* 노드 접근 */
+ ListNode *node = access(n0, 3);
+ cout << "연결 리스트에서 인덱스 3에 있는 노드의 값 = " << node->val << endl;
+
+ /* 노드 탐색 */
+ int index = find(n0, 2);
+ cout << "연결 리스트에서 값이 2인 노드의 인덱스 = " << index << endl;
+
+ // 메모리 해제
+ freeMemoryLinkedList(n0);
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_array_and_linkedlist/list.cpp b/ko/codes/cpp/chapter_array_and_linkedlist/list.cpp
new file mode 100644
index 000000000..57f1e58d1
--- /dev/null
+++ b/ko/codes/cpp/chapter_array_and_linkedlist/list.cpp
@@ -0,0 +1,72 @@
+/**
+ * File: list.cpp
+ * Created Time: 2022-11-25
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 실행 코드 */
+int main() {
+ /* 리스트 초기화 */
+ vector nums = {1, 3, 2, 5, 4};
+ cout << "리스트 nums = ";
+ printVector(nums);
+
+ /* 원소 접근 */
+ int num = nums[1];
+ cout << "인덱스 1의 원소에 접근하여 얻은 num = " << num << endl;
+
+ /* 원소 갱신 */
+ nums[1] = 0;
+ cout << "인덱스 1의 원소를 0으로 갱신한 후 nums = ";
+ printVector(nums);
+
+ /* 리스트 비우기 */
+ nums.clear();
+ cout << "리스트를 비운 후 nums = ";
+ printVector(nums);
+
+ /* 맨 뒤에 원소 추가 */
+ nums.push_back(1);
+ nums.push_back(3);
+ nums.push_back(2);
+ nums.push_back(5);
+ nums.push_back(4);
+ cout << "원소를 추가한 후 nums = ";
+ printVector(nums);
+
+ /* 중간에 원소 삽입 */
+ nums.insert(nums.begin() + 3, 6);
+ cout << "인덱스 3에 숫자 6을 삽입한 후 nums = ";
+ printVector(nums);
+
+ /* 원소 삭제 */
+ nums.erase(nums.begin() + 3);
+ cout << "인덱스 3의 원소를 삭제한 후 nums = ";
+ printVector(nums);
+
+ /* 인덱스를 통해 리스트를 순회한다 */
+ int count = 0;
+ for (int i = 0; i < nums.size(); i++) {
+ count += nums[i];
+ }
+ /* 리스트의 원소를 직접 순회한다 */
+ count = 0;
+ for (int x : nums) {
+ count += x;
+ }
+
+ /* 두 리스트 연결 */
+ vector nums1 = {6, 8, 7, 10, 9};
+ nums.insert(nums.end(), nums1.begin(), nums1.end());
+ cout << "리스트 nums1을 nums 뒤에 연결한 후 nums = ";
+ printVector(nums);
+
+ /* 리스트 정렬 */
+ sort(nums.begin(), nums.end());
+ cout << "리스트를 정렬한 후 nums = ";
+ printVector(nums);
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_array_and_linkedlist/my_list.cpp b/ko/codes/cpp/chapter_array_and_linkedlist/my_list.cpp
new file mode 100644
index 000000000..ffd5b56dc
--- /dev/null
+++ b/ko/codes/cpp/chapter_array_and_linkedlist/my_list.cpp
@@ -0,0 +1,171 @@
+/**
+ * File: my_list.cpp
+ * Created Time: 2022-11-25
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 리스트 클래스 */
+class MyList {
+ private:
+ int *arr; // 배열(리스트 원소 저장)
+ int arrCapacity = 10; // 리스트 용량
+ int arrSize = 0; // 리스트 길이(현재 원소 개수)
+ int extendRatio = 2; // 리스트의 용량을 확장할 때마다 적용하는 배수
+
+ public:
+ /* 생성자 */
+ MyList() {
+ arr = new int[arrCapacity];
+ }
+
+ /* 소멸자 */
+ ~MyList() {
+ delete[] arr;
+ }
+
+ /* 리스트 길이(현재 원소 개수) 반환*/
+ int size() {
+ return arrSize;
+ }
+
+ /* 리스트 용량 반환 */
+ int capacity() {
+ return arrCapacity;
+ }
+
+ /* 원소 접근 */
+ int get(int index) {
+ // 인덱스가 범위를 벗어나면 예외를 발생시킨다. 아래에서도 동일하다
+ if (index < 0 || index >= size())
+ throw out_of_range("인덱스가 범위를 벗어났다");
+ return arr[index];
+ }
+
+ /* 원소 갱신 */
+ void set(int index, int num) {
+ if (index < 0 || index >= size())
+ throw out_of_range("인덱스가 범위를 벗어났다");
+ arr[index] = num;
+ }
+
+ /* 맨 뒤에 원소 추가 */
+ void add(int num) {
+ // 원소 개수가 용량을 초과하면 용량 확장을 수행한다
+ if (size() == capacity())
+ extendCapacity();
+ arr[size()] = num;
+ // 원소 개수를 갱신한다
+ arrSize++;
+ }
+
+ /* 중간에 원소 삽입 */
+ void insert(int index, int num) {
+ if (index < 0 || index >= size())
+ throw out_of_range("인덱스가 범위를 벗어났다");
+ // 원소 개수가 용량을 초과하면 용량 확장을 수행한다
+ if (size() == capacity())
+ extendCapacity();
+ // 인덱스 index와 그 뒤의 원소를 모두 한 칸씩 뒤로 이동한다
+ for (int j = size() - 1; j >= index; j--) {
+ arr[j + 1] = arr[j];
+ }
+ arr[index] = num;
+ // 원소 개수를 갱신한다
+ arrSize++;
+ }
+
+ /* 원소 삭제 */
+ int remove(int index) {
+ if (index < 0 || index >= size())
+ throw out_of_range("인덱스가 범위를 벗어났다");
+ int num = arr[index];
+ // 인덱스 index 뒤의 원소를 모두 한 칸씩 앞으로 이동한다
+ for (int j = index; j < size() - 1; j++) {
+ arr[j] = arr[j + 1];
+ }
+ // 원소 개수를 갱신한다
+ arrSize--;
+ // 삭제한 원소를 반환한다
+ return num;
+ }
+
+ /* 리스트 용량 확장 */
+ void extendCapacity() {
+ // 길이가 기존 배열의 extendRatio배인 새 배열 생성
+ int newCapacity = capacity() * extendRatio;
+ int *tmp = arr;
+ arr = new int[newCapacity];
+ // 원래 배열의 모든 원소를 새 배열에 복사한다
+ for (int i = 0; i < size(); i++) {
+ arr[i] = tmp[i];
+ }
+ // 메모리 해제
+ delete[] tmp;
+ arrCapacity = newCapacity;
+ }
+
+ /* 출력을 위해 리스트를 Vector로 변환 */
+ vector toVector() {
+ // 유효한 길이 범위 안의 리스트 원소만 변환한다
+ vector vec(size());
+ for (int i = 0; i < size(); i++) {
+ vec[i] = arr[i];
+ }
+ return vec;
+ }
+};
+
+/* 실행 코드 */
+int main() {
+ /* 리스트 초기화 */
+ MyList *nums = new MyList();
+ /* 맨 뒤에 원소 추가 */
+ nums->add(1);
+ nums->add(3);
+ nums->add(2);
+ nums->add(5);
+ nums->add(4);
+ cout << "리스트 nums = ";
+ vector vec = nums->toVector();
+ printVector(vec);
+ cout << "용량 = " << nums->capacity() << " , 길이 = " << nums->size() << endl;
+
+ /* 중간에 원소 삽입 */
+ nums->insert(3, 6);
+ cout << "인덱스 3에 숫자 6을 삽입한 후 nums = ";
+ vec = nums->toVector();
+ printVector(vec);
+
+ /* 원소 삭제 */
+ nums->remove(3);
+ cout << "인덱스 3의 원소를 삭제한 후 nums = ";
+ vec = nums->toVector();
+ printVector(vec);
+
+ /* 원소 접근 */
+ int num = nums->get(1);
+ cout << "인덱스 1의 원소에 접근하여 얻은 num = " << num << endl;
+
+ /* 원소 갱신 */
+ nums->set(1, 0);
+ cout << "인덱스 1의 원소를 0으로 갱신한 후 nums = ";
+ vec = nums->toVector();
+ printVector(vec);
+
+ /* 용량 확장 동작 테스트 */
+ for (int i = 0; i < 10; i++) {
+ // i = 5일 때 리스트 길이가 용량을 초과하므로 용량 확장이 수행된다
+ nums->add(i);
+ }
+ cout << "용량을 확장한 후 리스트 nums = ";
+ vec = nums->toVector();
+ printVector(vec);
+ cout << "용량 = " << nums->capacity() << " , 길이 = " << nums->size() << endl;
+
+ // 메모리 해제
+ delete nums;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_backtracking/CMakeLists.txt b/ko/codes/cpp/chapter_backtracking/CMakeLists.txt
new file mode 100644
index 000000000..6c271e330
--- /dev/null
+++ b/ko/codes/cpp/chapter_backtracking/CMakeLists.txt
@@ -0,0 +1,10 @@
+add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.cpp)
+add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.cpp)
+add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.cpp)
+add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.cpp)
+add_executable(permutations_i permutations_i.cpp)
+add_executable(permutations_ii permutations_ii.cpp)
+add_executable(n_queens n_queens.cpp)
+add_executable(subset_sum_i_naive subset_sum_i_naive.cpp)
+add_executable(subset_sum_i subset_sum_i.cpp)
+add_executable(subset_sum_ii subset_sum_ii.cpp)
diff --git a/ko/codes/cpp/chapter_backtracking/n_queens.cpp b/ko/codes/cpp/chapter_backtracking/n_queens.cpp
new file mode 100644
index 000000000..6a3794f1b
--- /dev/null
+++ b/ko/codes/cpp/chapter_backtracking/n_queens.cpp
@@ -0,0 +1,65 @@
+/**
+ * File: n_queens.cpp
+ * Created Time: 2023-05-04
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 백트래킹 알고리즘: n-퀸 */
+void backtrack(int row, int n, vector> &state, vector>> &res, vector &cols,
+ vector &diags1, vector &diags2) {
+ // 모든 행에 배치했으면 해를 기록한다
+ if (row == n) {
+ res.push_back(state);
+ return;
+ }
+ // 모든 열을 순회한다
+ for (int col = 0; col < n; col++) {
+ // 해당 칸이 속한 주대각선과 부대각선을 계산한다
+ int diag1 = row - col + n - 1;
+ int diag2 = row + col;
+ // 가지치기: 해당 칸이 속한 열, 주대각선, 부대각선에 퀸이 있으면 안 된다
+ if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {
+ // 시도: 해당 칸에 퀸을 놓는다
+ state[row][col] = "Q";
+ cols[col] = diags1[diag1] = diags2[diag2] = true;
+ // 다음 행에 배치한다
+ backtrack(row + 1, n, state, res, cols, diags1, diags2);
+ // 되돌리기: 해당 칸을 빈칸으로 되돌린다
+ state[row][col] = "#";
+ cols[col] = diags1[diag1] = diags2[diag2] = false;
+ }
+ }
+}
+
+/* n-퀸 문제 풀기 */
+vector>> nQueens(int n) {
+ // n*n 크기의 체스판을 초기화한다. 'Q'는 퀸, '#'은 빈칸을 나타낸다
+ vector> state(n, vector(n, "#"));
+ vector cols(n, false); // 열에 퀸이 있는지 기록한다
+ vector diags1(2 * n - 1, false); // 주대각선에 퀸이 있는지 기록한다
+ vector diags2(2 * n - 1, false); // 부대각선에 퀸이 있는지 기록한다
+ vector>> res;
+
+ backtrack(0, n, state, res, cols, diags1, diags2);
+
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 4;
+ vector>> res = nQueens(n);
+
+ cout << "입력한 체스판의 가로와 세로 길이: " << n << endl;
+ cout << "퀸을 배치하는 방법은 총 " << res.size() << " 가지이다" << endl;
+ for (const vector> &state : res) {
+ cout << "--------------------" << endl;
+ for (const vector &row : state) {
+ printVector(row);
+ }
+ }
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_backtracking/permutations_i.cpp b/ko/codes/cpp/chapter_backtracking/permutations_i.cpp
new file mode 100644
index 000000000..a3beee463
--- /dev/null
+++ b/ko/codes/cpp/chapter_backtracking/permutations_i.cpp
@@ -0,0 +1,54 @@
+/**
+ * File: permutations_i.cpp
+ * Created Time: 2023-04-24
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 백트래킹 알고리즘: 모든 순열 I */
+void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) {
+ // 상태의 길이가 원소 개수와 같으면 해를 기록한다
+ if (state.size() == choices.size()) {
+ res.push_back(state);
+ return;
+ }
+ // 모든 선택지를 순회한다
+ for (int i = 0; i < choices.size(); i++) {
+ int choice = choices[i];
+ // 가지치기: 원소를 중복하여 선택할 수 없다
+ if (!selected[i]) {
+ // 시도: 선택하고 상태를 갱신한다
+ selected[i] = true;
+ state.push_back(choice);
+ // 다음 선택을 진행한다
+ backtrack(state, choices, selected, res);
+ // 되돌리기: 선택을 취소하고 이전 상태로 되돌린다
+ selected[i] = false;
+ state.pop_back();
+ }
+ }
+}
+
+/* 모든 순열 I */
+vector> permutationsI(vector nums) {
+ vector state;
+ vector selected(nums.size(), false);
+ vector> res;
+ backtrack(state, nums, selected, res);
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ vector nums = {1, 2, 3};
+
+ vector> res = permutationsI(nums);
+
+ cout << "입력 배열 nums = ";
+ printVector(nums);
+ cout << "모든 순열 res = ";
+ printVectorMatrix(res);
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_backtracking/permutations_ii.cpp b/ko/codes/cpp/chapter_backtracking/permutations_ii.cpp
new file mode 100644
index 000000000..4d3e60c90
--- /dev/null
+++ b/ko/codes/cpp/chapter_backtracking/permutations_ii.cpp
@@ -0,0 +1,56 @@
+/**
+ * File: permutations_ii.cpp
+ * Created Time: 2023-04-24
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 백트래킹 알고리즘: 모든 순열 II */
+void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) {
+ // 상태의 길이가 원소 개수와 같으면 해를 기록한다
+ if (state.size() == choices.size()) {
+ res.push_back(state);
+ return;
+ }
+ // 모든 선택지를 순회한다
+ unordered_set duplicated;
+ for (int i = 0; i < choices.size(); i++) {
+ int choice = choices[i];
+ // 가지치기: 원소를 중복하여 선택할 수 없으며, 값이 같은 원소도 중복하여 선택할 수 없다
+ if (!selected[i] && duplicated.find(choice) == duplicated.end()) {
+ // 시도: 선택하고 상태를 갱신한다
+ duplicated.emplace(choice); // 선택한 원소의 값을 기록한다
+ selected[i] = true;
+ state.push_back(choice);
+ // 다음 선택을 진행한다
+ backtrack(state, choices, selected, res);
+ // 되돌리기: 선택을 취소하고 이전 상태로 되돌린다
+ selected[i] = false;
+ state.pop_back();
+ }
+ }
+}
+
+/* 모든 순열 II */
+vector> permutationsII(vector nums) {
+ vector state;
+ vector selected(nums.size(), false);
+ vector> res;
+ backtrack(state, nums, selected, res);
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ vector nums = {1, 1, 2};
+
+ vector> res = permutationsII(nums);
+
+ cout << "입력 배열 nums = ";
+ printVector(nums);
+ cout << "모든 순열 res = ";
+ printVectorMatrix(res);
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp b/ko/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp
new file mode 100644
index 000000000..601245365
--- /dev/null
+++ b/ko/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp
@@ -0,0 +1,39 @@
+/**
+ * File: preorder_traversal_i_compact.cpp
+ * Created Time: 2023-04-16
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+vector res;
+
+/* 전위 순회: 예제 1 */
+void preOrder(TreeNode *root) {
+ if (root == nullptr) {
+ return;
+ }
+ if (root->val == 7) {
+ // 해를 기록한다
+ res.push_back(root);
+ }
+ preOrder(root->left);
+ preOrder(root->right);
+}
+
+/* 실행 코드 */
+int main() {
+ TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7});
+ cout << "\n이진 트리 초기화" << endl;
+ printTree(root);
+
+ // 전위 순회
+ preOrder(root);
+
+ cout << "\n값이 7인 모든 노드 출력" << endl;
+ vector vals;
+ for (TreeNode *node : res) {
+ vals.push_back(node->val);
+ }
+ printVector(vals);
+}
diff --git a/ko/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp b/ko/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp
new file mode 100644
index 000000000..872be419f
--- /dev/null
+++ b/ko/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp
@@ -0,0 +1,46 @@
+/**
+ * File: preorder_traversal_ii_compact.cpp
+ * Created Time: 2023-04-16
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+vector path;
+vector> res;
+
+/* 전위 순회: 예제 2 */
+void preOrder(TreeNode *root) {
+ if (root == nullptr) {
+ return;
+ }
+ // 시도
+ path.push_back(root);
+ if (root->val == 7) {
+ // 해를 기록한다
+ res.push_back(path);
+ }
+ preOrder(root->left);
+ preOrder(root->right);
+ // 되돌리기
+ path.pop_back();
+}
+
+/* 실행 코드 */
+int main() {
+ TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7});
+ cout << "\n이진 트리 초기화" << endl;
+ printTree(root);
+
+ // 전위 순회
+ preOrder(root);
+
+ cout << "\n루트 노드에서 노드 7까지의 모든 경로 출력" << endl;
+ for (vector &path : res) {
+ vector vals;
+ for (TreeNode *node : path) {
+ vals.push_back(node->val);
+ }
+ printVector(vals);
+ }
+}
diff --git a/ko/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp b/ko/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp
new file mode 100644
index 000000000..09dadb192
--- /dev/null
+++ b/ko/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp
@@ -0,0 +1,47 @@
+/**
+ * File: preorder_traversal_iii_compact.cpp
+ * Created Time: 2023-04-16
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+vector path;
+vector> res;
+
+/* 전위 순회: 예제 3 */
+void preOrder(TreeNode *root) {
+ // 가지치기
+ if (root == nullptr || root->val == 3) {
+ return;
+ }
+ // 시도
+ path.push_back(root);
+ if (root->val == 7) {
+ // 해를 기록한다
+ res.push_back(path);
+ }
+ preOrder(root->left);
+ preOrder(root->right);
+ // 되돌리기
+ path.pop_back();
+}
+
+/* 실행 코드 */
+int main() {
+ TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7});
+ cout << "\n이진 트리 초기화" << endl;
+ printTree(root);
+
+ // 전위 순회
+ preOrder(root);
+
+ cout << "\n값이 3인 노드를 포함하지 않는 조건으로, 루트 노드에서 노드 7까지의 모든 경로 출력" << endl;
+ for (vector &path : res) {
+ vector vals;
+ for (TreeNode *node : path) {
+ vals.push_back(node->val);
+ }
+ printVector(vals);
+ }
+}
diff --git a/ko/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp b/ko/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp
new file mode 100644
index 000000000..6b90a1060
--- /dev/null
+++ b/ko/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp
@@ -0,0 +1,76 @@
+/**
+ * File: preorder_traversal_iii_template.cpp
+ * Created Time: 2023-04-16
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 현재 상태가 해인지 판단 */
+bool isSolution(vector &state) {
+ return !state.empty() && state.back()->val == 7;
+}
+
+/* 해를 기록한다 */
+void recordSolution(vector &state, vector> &res) {
+ res.push_back(state);
+}
+
+/* 현재 상태에서 해당 선택이 유효한지 판단 */
+bool isValid(vector &state, TreeNode *choice) {
+ return choice != nullptr && choice->val != 3;
+}
+
+/* 상태 갱신 */
+void makeChoice(vector &state, TreeNode *choice) {
+ state.push_back(choice);
+}
+
+/* 상태 복원 */
+void undoChoice(vector &state, TreeNode *choice) {
+ state.pop_back();
+}
+
+/* 백트래킹 알고리즘: 예제 3 */
+void backtrack(vector &state, vector &choices, vector> &res) {
+ // 해인지 확인한다
+ if (isSolution(state)) {
+ // 해를 기록한다
+ recordSolution(state, res);
+ }
+ // 모든 선택지를 순회한다
+ for (TreeNode *choice : choices) {
+ // 가지치기: 선택이 유효한지 확인한다
+ if (isValid(state, choice)) {
+ // 시도: 선택하고 상태를 갱신한다
+ makeChoice(state, choice);
+ // 다음 선택을 진행한다
+ vector nextChoices{choice->left, choice->right};
+ backtrack(state, nextChoices, res);
+ // 되돌리기: 선택을 취소하고 이전 상태로 되돌린다
+ undoChoice(state, choice);
+ }
+ }
+}
+
+/* 실행 코드 */
+int main() {
+ TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7});
+ cout << "\n이진 트리 초기화" << endl;
+ printTree(root);
+
+ // 백트래킹 알고리즘
+ vector state;
+ vector choices = {root};
+ vector> res;
+ backtrack(state, choices, res);
+
+ cout << "\n값이 3인 노드를 포함하지 않는 조건으로, 루트 노드에서 노드 7까지의 모든 경로 출력" << endl;
+ for (vector &path : res) {
+ vector vals;
+ for (TreeNode *node : path) {
+ vals.push_back(node->val);
+ }
+ printVector(vals);
+ }
+}
diff --git a/ko/codes/cpp/chapter_backtracking/subset_sum_i.cpp b/ko/codes/cpp/chapter_backtracking/subset_sum_i.cpp
new file mode 100644
index 000000000..6a5678dd4
--- /dev/null
+++ b/ko/codes/cpp/chapter_backtracking/subset_sum_i.cpp
@@ -0,0 +1,57 @@
+/**
+ * File: subset_sum_i.cpp
+ * Created Time: 2023-06-21
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 백트래킹 알고리즘: 부분 집합 합 I */
+void backtrack(vector &state, int target, vector &choices, int start, vector> &res) {
+ // 부분 집합의 합이 target과 같으면 해를 기록한다
+ if (target == 0) {
+ res.push_back(state);
+ return;
+ }
+ // 모든 선택지를 순회한다
+ // 가지치기 2: start부터 순회하여 중복 부분 집합 생성을 방지한다
+ for (int i = start; i < choices.size(); i++) {
+ // 가지치기 1: 부분 집합의 합이 target을 초과하면 즉시 반복문을 종료한다
+ // 배열이 정렬되어 있어 뒤의 원소가 더 크므로, 부분 집합의 합이 반드시 target을 초과하기 때문이다
+ if (target - choices[i] < 0) {
+ break;
+ }
+ // 시도: 선택하고 target, start를 갱신한다
+ state.push_back(choices[i]);
+ // 다음 선택을 진행한다
+ backtrack(state, target - choices[i], choices, i, res);
+ // 되돌리기: 선택을 취소하고 이전 상태로 되돌린다
+ state.pop_back();
+ }
+}
+
+/* 부분 집합 합 I 풀기 */
+vector> subsetSumI(vector &nums, int target) {
+ vector state; // 상태(부분 집합)
+ sort(nums.begin(), nums.end()); // nums를 정렬한다
+ int start = 0; // 순회 시작 위치
+ vector> res; // 결과 리스트(부분 집합 리스트)
+ backtrack(state, target, nums, start, res);
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ vector nums = {3, 4, 5};
+ int target = 9;
+
+ vector> res = subsetSumI(nums, target);
+
+ cout << "입력 배열 nums = ";
+ printVector(nums);
+ cout << "target = " << target << endl;
+ cout << "부분 집합의 합: " << target << " ; 해당하는 모든 부분 집합 res = " << endl;
+ printVectorMatrix(res);
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp b/ko/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp
new file mode 100644
index 000000000..e8a92eab6
--- /dev/null
+++ b/ko/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp
@@ -0,0 +1,54 @@
+/**
+ * File: subset_sum_i_naive.cpp
+ * Created Time: 2023-06-21
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 백트래킹 알고리즘: 부분 집합 합 I */
+void backtrack(vector &state, int target, int total, vector &choices, vector> &res) {
+ // 부분 집합의 합이 target과 같으면 해를 기록한다
+ if (total == target) {
+ res.push_back(state);
+ return;
+ }
+ // 모든 선택지를 순회한다
+ for (size_t i = 0; i < choices.size(); i++) {
+ // 가지치기: 부분 집합의 합이 target을 초과하면 해당 선택을 건너뛴다
+ if (total + choices[i] > target) {
+ continue;
+ }
+ // 시도: 선택하고 원소의 합 total을 갱신한다
+ state.push_back(choices[i]);
+ // 다음 선택을 진행한다
+ backtrack(state, target, total + choices[i], choices, res);
+ // 되돌리기: 선택을 취소하고 이전 상태로 되돌린다
+ state.pop_back();
+ }
+}
+
+/* 부분 집합 합 I 풀기(중복 부분 집합 포함) */
+vector> subsetSumINaive(vector &nums, int target) {
+ vector state; // 상태(부분 집합)
+ int total = 0; // 부분 집합의 합
+ vector> res; // 결과 리스트(부분 집합 리스트)
+ backtrack(state, target, total, nums, res);
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ vector nums = {3, 4, 5};
+ int target = 9;
+
+ vector> res = subsetSumINaive(nums, target);
+
+ cout << "입력 배열 nums = ";
+ printVector(nums);
+ cout << "target = " << target << endl;
+ cout << "부분 집합의 합: " << target << " ; 해당하는 모든 부분 집합 res = " << endl;
+ printVectorMatrix(res);
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_backtracking/subset_sum_ii.cpp b/ko/codes/cpp/chapter_backtracking/subset_sum_ii.cpp
new file mode 100644
index 000000000..6ac52bced
--- /dev/null
+++ b/ko/codes/cpp/chapter_backtracking/subset_sum_ii.cpp
@@ -0,0 +1,62 @@
+/**
+ * File: subset_sum_ii.cpp
+ * Created Time: 2023-06-21
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 백트래킹 알고리즘: 부분 집합 합 II */
+void backtrack(vector &state, int target, vector &choices, int start, vector> &res) {
+ // 부분 집합의 합이 target과 같으면 해를 기록한다
+ if (target == 0) {
+ res.push_back(state);
+ return;
+ }
+ // 모든 선택지를 순회한다
+ // 가지치기 2: start부터 순회하여 중복 부분 집합 생성을 방지한다
+ // 가지치기 3: start부터 순회하여 같은 원소의 중복 선택을 방지한다
+ for (int i = start; i < choices.size(); i++) {
+ // 가지치기 1: 부분 집합의 합이 target을 초과하면 즉시 반복문을 종료한다
+ // 배열이 정렬되어 있어 뒤의 원소가 더 크므로, 부분 집합의 합이 반드시 target을 초과하기 때문이다
+ if (target - choices[i] < 0) {
+ break;
+ }
+ // 가지치기 4: 해당 원소가 왼쪽 원소와 같으면 탐색 분기가 중복되므로 바로 건너뛴다
+ if (i > start && choices[i] == choices[i - 1]) {
+ continue;
+ }
+ // 시도: 선택하고 target, start를 갱신한다
+ state.push_back(choices[i]);
+ // 다음 선택을 진행한다
+ backtrack(state, target - choices[i], choices, i + 1, res);
+ // 되돌리기: 선택을 취소하고 이전 상태로 되돌린다
+ state.pop_back();
+ }
+}
+
+/* 부분 집합 합 II 풀기 */
+vector> subsetSumII(vector &nums, int target) {
+ vector state; // 상태(부분 집합)
+ sort(nums.begin(), nums.end()); // nums를 정렬한다
+ int start = 0; // 순회 시작 위치
+ vector> res; // 결과 리스트(부분 집합 리스트)
+ backtrack(state, target, nums, start, res);
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ vector nums = {4, 4, 5};
+ int target = 9;
+
+ vector> res = subsetSumII(nums, target);
+
+ cout << "입력 배열 nums = ";
+ printVector(nums);
+ cout << "target = " << target << endl;
+ cout << "부분 집합의 합: " << target << " ; 해당하는 모든 부분 집합 res = " << endl;
+ printVectorMatrix(res);
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_computational_complexity/CMakeLists.txt b/ko/codes/cpp/chapter_computational_complexity/CMakeLists.txt
new file mode 100644
index 000000000..536935b68
--- /dev/null
+++ b/ko/codes/cpp/chapter_computational_complexity/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_executable(iteration iteration.cpp)
+add_executable(recursion recursion.cpp)
+add_executable(space_complexity space_complexity.cpp)
+add_executable(time_complexity time_complexity.cpp)
+add_executable(worst_best_time_complexity worst_best_time_complexity.cpp)
+add_executable(complexity_exercises complexity_exercises.cpp)
diff --git a/ko/codes/cpp/chapter_computational_complexity/complexity_exercises.cpp b/ko/codes/cpp/chapter_computational_complexity/complexity_exercises.cpp
new file mode 100644
index 000000000..4dc0252e7
--- /dev/null
+++ b/ko/codes/cpp/chapter_computational_complexity/complexity_exercises.cpp
@@ -0,0 +1,61 @@
+/**
+ * File: complexity_exercises.cpp
+ * Created Time: 2026-08-18
+ * Author: Hello Algo Team
+ */
+
+#include
+
+/* 반복으로 합 구하기 */
+int sumIter(int n) {
+ int res = 0;
+ for (int i = 1; i <= n; ++i) {
+ res += i;
+ }
+ return res;
+}
+
+/* 재귀로 합 구하기 */
+int sumRecur(int n) {
+ if (n == 1) {
+ return 1;
+ }
+ return n + sumRecur(n - 1);
+}
+
+/* 선형 차수 반복문 */
+int linearLoop(int n) {
+ int res = 0;
+ for (int i = 0; i < n; ++i) {
+ res += i;
+ }
+ return res;
+}
+
+/* 제곱 차수 반복문 */
+int quadraticLoop(int n) {
+ int res = 0;
+ for (int i = 0; i < n; ++i) {
+ for (int j = i; j < n; ++j) {
+ res += j;
+ }
+ }
+ return res;
+}
+
+/* 로그 차수 반복문 */
+int logarithmicLoop(int n) {
+ while (n > 1) {
+ n /= 2;
+ }
+ return n;
+}
+
+int main() {
+ assert(sumIter(1) == 1 && sumRecur(1) == 1);
+ assert(sumIter(4) == 10 && sumRecur(4) == 10);
+ assert(linearLoop(4) == 6);
+ assert(quadraticLoop(4) == 20);
+ assert(logarithmicLoop(4) == 1 && logarithmicLoop(5) == 1);
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_computational_complexity/iteration.cpp b/ko/codes/cpp/chapter_computational_complexity/iteration.cpp
new file mode 100644
index 000000000..60dc4b5ef
--- /dev/null
+++ b/ko/codes/cpp/chapter_computational_complexity/iteration.cpp
@@ -0,0 +1,76 @@
+/**
+ * File: iteration.cpp
+ * Created Time: 2023-08-24
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* for 반복문 */
+int forLoop(int n) {
+ int res = 0;
+ // 반복문으로 1, 2, ..., n-1, n의 합을 구한다
+ for (int i = 1; i <= n; ++i) {
+ res += i;
+ }
+ return res;
+}
+
+/* while 반복문 */
+int whileLoop(int n) {
+ int res = 0;
+ int i = 1; // 조건 변수를 초기화한다
+ // 반복문으로 1, 2, ..., n-1, n의 합을 구한다
+ while (i <= n) {
+ res += i;
+ i++; // 조건 변수를 갱신한다
+ }
+ return res;
+}
+
+/* while 반복문(두 번 갱신) */
+int whileLoopII(int n) {
+ int res = 0;
+ int i = 1; // 조건 변수를 초기화한다
+ // 반복문으로 1, 4, 10, ...의 합을 구한다
+ while (i <= n) {
+ res += i;
+ // 조건 변수를 갱신한다
+ i++;
+ i *= 2;
+ }
+ return res;
+}
+
+/* 이중 for 반복문 */
+string nestedForLoop(int n) {
+ ostringstream res;
+ // i = 1, 2, ..., n-1, n을 반복한다
+ for (int i = 1; i <= n; ++i) {
+ // j = 1, 2, ..., n-1, n을 반복한다
+ for (int j = 1; j <= n; ++j) {
+ res << "(" << i << ", " << j << "), ";
+ }
+ }
+ return res.str();
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 5;
+ int res;
+
+ res = forLoop(n);
+ cout << "\nfor 반복문으로 구한 합 res = " << res << endl;
+
+ res = whileLoop(n);
+ cout << "\nwhile 반복문으로 구한 합 res = " << res << endl;
+
+ res = whileLoopII(n);
+ cout << "\nwhile 반복문(두 번 갱신)으로 구한 합 res = " << res << endl;
+
+ string resStr = nestedForLoop(n);
+ cout << "\n이중 for 반복문의 순회 결과 " << resStr << endl;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_computational_complexity/recursion.cpp b/ko/codes/cpp/chapter_computational_complexity/recursion.cpp
new file mode 100644
index 000000000..6ced17bb5
--- /dev/null
+++ b/ko/codes/cpp/chapter_computational_complexity/recursion.cpp
@@ -0,0 +1,78 @@
+/**
+ * File: recursion.cpp
+ * Created Time: 2023-08-24
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 재귀 */
+int recur(int n) {
+ // 종료 조건
+ if (n == 1)
+ return 1;
+ // 내려가기: 재귀 호출
+ int res = recur(n - 1);
+ // 돌아오기: 결과 반환
+ return n + res;
+}
+
+/* 반복으로 재귀 구현 */
+int forLoopRecur(int n) {
+ // 명시적인 스택을 사용하여 시스템 호출 스택을 모방한다
+ stack stack;
+ int res = 0;
+ // 내려가기: 재귀 호출
+ for (int i = n; i > 0; i--) {
+ // 스택에 삽입하여 내려가는 과정을 모방한다
+ stack.push(i);
+ }
+ // 돌아오기: 결과 반환
+ while (!stack.empty()) {
+ // 스택에서 꺼내어 돌아오는 과정을 모방한다
+ res += stack.top();
+ stack.pop();
+ }
+ // res = 1+2+3+...+n
+ return res;
+}
+
+/* 꼬리 재귀 */
+int tailRecur(int n, int res) {
+ // 종료 조건
+ if (n == 0)
+ return res;
+ // 꼬리 재귀 호출
+ return tailRecur(n - 1, res + n);
+}
+
+/* 피보나치 수열: 재귀 */
+int fib(int n) {
+ // 종료 조건 f(1) = 0, f(2) = 1
+ if (n == 1 || n == 2)
+ return n - 1;
+ // 재귀 호출 f(n) = f(n-1) + f(n-2)
+ int res = fib(n - 1) + fib(n - 2);
+ // 결과 f(n)을 반환한다
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 5;
+ int res;
+
+ res = recur(n);
+ cout << "\n재귀 함수로 구한 합 res = " << res << endl;
+
+ res = forLoopRecur(n);
+ cout << "\n반복으로 재귀를 구현하여 구한 합 res = " << res << endl;
+
+ res = tailRecur(n, 0);
+ cout << "\n꼬리 재귀 함수로 구한 합 res = " << res << endl;
+
+ res = fib(n);
+ cout << "\n피보나치 수열의 항 번호: " << n << " ; 해당 항의 값: " << res << endl;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_computational_complexity/space_complexity.cpp b/ko/codes/cpp/chapter_computational_complexity/space_complexity.cpp
new file mode 100644
index 000000000..d5287474e
--- /dev/null
+++ b/ko/codes/cpp/chapter_computational_complexity/space_complexity.cpp
@@ -0,0 +1,107 @@
+/**
+ * File: space_complexity.cpp
+ * Created Time: 2022-11-25
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 함수 */
+int func() {
+ // 몇 가지 연산을 수행한다
+ return 0;
+}
+
+/* 상수 차수 */
+void constant(int n) {
+ // 상수, 변수, 객체는 O(1) 공간을 차지한다
+ const int a = 0;
+ int b = 0;
+ vector nums(10000);
+ ListNode node(0);
+ // 반복문 안의 변수는 O(1) 공간을 차지한다
+ for (int i = 0; i < n; i++) {
+ int c = 0;
+ }
+ // 반복문 안의 함수는 O(1) 공간을 차지한다
+ for (int i = 0; i < n; i++) {
+ func();
+ }
+}
+
+/* 선형 차수 */
+void linear(int n) {
+ // 길이가 n인 배열은 O(n) 공간을 차지한다
+ vector nums(n);
+ // 길이가 n인 리스트는 O(n) 공간을 차지한다
+ vector nodes;
+ for (int i = 0; i < n; i++) {
+ nodes.push_back(ListNode(i));
+ }
+ // 길이가 n인 해시 테이블은 O(n) 공간을 차지한다
+ unordered_map map;
+ for (int i = 0; i < n; i++) {
+ map[i] = to_string(i);
+ }
+}
+
+/* 선형 차수(재귀 구현) */
+void linearRecur(int n) {
+ cout << "재귀 n = " << n << endl;
+ if (n == 1)
+ return;
+ linearRecur(n - 1);
+}
+
+/* 제곱 차수 */
+void quadratic(int n) {
+ // 이차원 리스트는 O(n^2) 공간을 차지한다
+ vector> numMatrix;
+ for (int i = 0; i < n; i++) {
+ vector tmp;
+ for (int j = 0; j < n; j++) {
+ tmp.push_back(0);
+ }
+ numMatrix.push_back(tmp);
+ }
+}
+
+/* 제곱 차수(재귀 구현) */
+int quadraticRecur(int n) {
+ if (n <= 0)
+ return 0;
+ vector nums(n);
+ cout << "재귀 n = " << n << " 일 때 nums의 길이 = " << nums.size() << endl;
+ return quadraticRecur(n - 1);
+}
+
+/* 지수 차수(포화 이진 트리 구성) */
+TreeNode *buildTree(int n) {
+ if (n == 0)
+ return nullptr;
+ TreeNode *root = new TreeNode(0);
+ root->left = buildTree(n - 1);
+ root->right = buildTree(n - 1);
+ return root;
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 5;
+ // 상수 차수
+ constant(n);
+ // 선형 차수
+ linear(n);
+ linearRecur(n);
+ // 제곱 차수
+ quadratic(n);
+ quadraticRecur(n);
+ // 지수 차수
+ TreeNode *root = buildTree(n);
+ printTree(root);
+
+ // 메모리 해제
+ freeMemoryTree(root);
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_computational_complexity/time_complexity.cpp b/ko/codes/cpp/chapter_computational_complexity/time_complexity.cpp
new file mode 100644
index 000000000..f04ac6d21
--- /dev/null
+++ b/ko/codes/cpp/chapter_computational_complexity/time_complexity.cpp
@@ -0,0 +1,168 @@
+/**
+ * File: time_complexity.cpp
+ * Created Time: 2022-11-25
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 상수 차수 */
+int constant(int n) {
+ int count = 0;
+ int size = 100000;
+ for (int i = 0; i < size; i++)
+ count++;
+ return count;
+}
+
+/* 선형 차수 */
+int linear(int n) {
+ int count = 0;
+ for (int i = 0; i < n; i++)
+ count++;
+ return count;
+}
+
+/* 선형 차수(배열 순회) */
+int arrayTraversal(vector &nums) {
+ int count = 0;
+ // 반복 횟수는 배열 길이에 비례한다
+ for (int num : nums) {
+ count++;
+ }
+ return count;
+}
+
+/* 제곱 차수 */
+int quadratic(int n) {
+ int count = 0;
+ // 반복 횟수는 데이터 크기 n의 제곱에 비례한다
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < n; j++) {
+ count++;
+ }
+ }
+ return count;
+}
+
+/* 제곱 차수(버블 정렬) */
+int bubbleSort(vector &nums) {
+ int count = 0; // 카운터
+ // 바깥쪽 반복문: 정렬되지 않은 구간은 [0, i]이다
+ for (int i = nums.size() - 1; i > 0; i--) {
+ // 안쪽 반복문: 정렬되지 않은 구간 [0, i]의 최댓값을 교환하여 이 구간의 맨 오른쪽으로 옮긴다
+ for (int j = 0; j < i; j++) {
+ if (nums[j] > nums[j + 1]) {
+ // nums[j]와 nums[j + 1]을 교환한다
+ int tmp = nums[j];
+ nums[j] = nums[j + 1];
+ nums[j + 1] = tmp;
+ count += 3; // 원소 교환에는 단위 연산 3회가 포함된다
+ }
+ }
+ }
+ return count;
+}
+
+/* 지수 차수(반복문 구현) */
+int exponential(int n) {
+ int count = 0, base = 1;
+ // 세포가 매번 둘로 나뉘어 수열 1, 2, 4, 8, ..., 2^(n-1)을 이룬다
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < base; j++) {
+ count++;
+ }
+ base *= 2;
+ }
+ // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
+ return count;
+}
+
+/* 지수 차수(재귀 구현) */
+int expRecur(int n) {
+ if (n == 1)
+ return 1;
+ return expRecur(n - 1) + expRecur(n - 1) + 1;
+}
+
+/* 로그 차수(반복문 구현) */
+int logarithmic(int n) {
+ int count = 0;
+ while (n > 1) {
+ n = n / 2;
+ count++;
+ }
+ return count;
+}
+
+/* 로그 차수(재귀 구현) */
+int logRecur(int n) {
+ if (n <= 1)
+ return 0;
+ return logRecur(n / 2) + 1;
+}
+
+/* 선형 로그 차수 */
+int linearLogRecur(int n) {
+ if (n <= 1)
+ return 1;
+ int count = linearLogRecur(n / 2) + linearLogRecur(n / 2);
+ for (int i = 0; i < n; i++) {
+ count++;
+ }
+ return count;
+}
+
+/* 팩토리얼 차수(재귀 구현) */
+int factorialRecur(int n) {
+ if (n == 0)
+ return 1;
+ int count = 0;
+ // 하나에서 n개로 분열한다
+ for (int i = 0; i < n; i++) {
+ count += factorialRecur(n - 1);
+ }
+ return count;
+}
+
+/* 실행 코드 */
+int main() {
+ // n을 바꾸어 실행하면서 각 복잡도에서 연산 횟수가 어떻게 변하는지 살펴볼 수 있다
+ int n = 8;
+ cout << "입력 데이터 크기 n = " << n << endl;
+
+ int count = constant(n);
+ cout << "상수 차수의 연산 횟수 = " << count << endl;
+
+ count = linear(n);
+ cout << "선형 차수의 연산 횟수 = " << count << endl;
+ vector arr(n);
+ count = arrayTraversal(arr);
+ cout << "선형 차수(배열 순회)의 연산 횟수 = " << count << endl;
+
+ count = quadratic(n);
+ cout << "제곱 차수의 연산 횟수 = " << count << endl;
+ vector nums(n);
+ for (int i = 0; i < n; i++)
+ nums[i] = n - i; // [n,n-1,...,2,1]
+ count = bubbleSort(nums);
+ cout << "제곱 차수(버블 정렬)의 연산 횟수 = " << count << endl;
+
+ count = exponential(n);
+ cout << "지수 차수(반복문 구현)의 연산 횟수 = " << count << endl;
+ count = expRecur(n);
+ cout << "지수 차수(재귀 구현)의 연산 횟수 = " << count << endl;
+
+ count = logarithmic(n);
+ cout << "로그 차수(반복문 구현)의 연산 횟수 = " << count << endl;
+ count = logRecur(n);
+ cout << "로그 차수(재귀 구현)의 연산 횟수 = " << count << endl;
+
+ count = linearLogRecur(n);
+ cout << "선형 로그 차수(재귀 구현)의 연산 횟수 = " << count << endl;
+
+ count = factorialRecur(n);
+ cout << "팩토리얼 차수(재귀 구현)의 연산 횟수 = " << count << endl;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp b/ko/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp
new file mode 100644
index 000000000..8a58b1e42
--- /dev/null
+++ b/ko/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp
@@ -0,0 +1,45 @@
+/**
+ * File: worst_best_time_complexity.cpp
+ * Created Time: 2022-11-25
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 원소가 { 1, 2, ..., n }이며 순서가 섞인 배열 생성 */
+vector randomNumbers(int n) {
+ vector nums(n);
+ // 배열 nums = { 1, 2, 3, ..., n }을 생성한다
+ for (int i = 0; i < n; i++) {
+ nums[i] = i + 1;
+ }
+ // 시스템 시간으로 난수 시드를 생성한다
+ unsigned seed = chrono::system_clock::now().time_since_epoch().count();
+ // 배열 원소를 무작위로 섞는다
+ shuffle(nums.begin(), nums.end(), default_random_engine(seed));
+ return nums;
+}
+
+/* 배열 nums에서 숫자 1이 있는 인덱스 탐색 */
+int findOne(vector &nums) {
+ for (int i = 0; i < nums.size(); i++) {
+ // 원소 1이 배열 맨 앞에 있으면 최선 시간 복잡도 O(1)에 도달한다
+ // 원소 1이 배열 맨 뒤에 있으면 최악 시간 복잡도 O(n)에 도달한다
+ if (nums[i] == 1)
+ return i;
+ }
+ return -1;
+}
+
+/* 실행 코드 */
+int main() {
+ for (int i = 0; i < 1000; i++) {
+ int n = 100;
+ vector nums = randomNumbers(n);
+ int index = findOne(nums);
+ cout << "\n배열 [ 1, 2, ..., n ]을 섞은 결과 = ";
+ printVector(nums);
+ cout << "숫자 1의 인덱스 " << index << endl;
+ }
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt b/ko/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt
new file mode 100644
index 000000000..8e66f77b5
--- /dev/null
+++ b/ko/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_executable(binary_search_recur binary_search_recur.cpp)
+add_executable(build_tree build_tree.cpp)
+add_executable(hanota hanota.cpp)
+add_executable(fast_power fast_power.cpp)
diff --git a/ko/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp b/ko/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp
new file mode 100644
index 000000000..ca09e4f1b
--- /dev/null
+++ b/ko/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp
@@ -0,0 +1,46 @@
+/**
+ * File: binary_search_recur.cpp
+ * Created Time: 2023-07-17
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 이진 탐색: 문제 f(i, j) */
+int dfs(vector &nums, int target, int i, int j) {
+ // 구간이 비어 있으면 목표 원소가 없다는 뜻이므로 -1을 반환한다
+ if (i > j) {
+ return -1;
+ }
+ // 중간 인덱스 m을 계산한다
+ int m = (i + j) / 2;
+ if (nums[m] < target) {
+ // 부분 문제 f(m+1, j)를 재귀적으로 푼다
+ return dfs(nums, target, m + 1, j);
+ } else if (nums[m] > target) {
+ // 부분 문제 f(i, m-1)를 재귀적으로 푼다
+ return dfs(nums, target, i, m - 1);
+ } else {
+ // 목표 원소를 찾았으므로 해당 인덱스를 반환한다
+ return m;
+ }
+}
+
+/* 이진 탐색 */
+int binarySearch(vector &nums, int target) {
+ int n = nums.size();
+ // 문제 f(0, n-1)을 푼다
+ return dfs(nums, target, 0, n - 1);
+}
+
+/* 실행 코드 */
+int main() {
+ int target = 6;
+ vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35};
+
+ // 이진 탐색(양 끝을 포함하는 닫힌 구간)
+ int index = binarySearch(nums, target);
+ cout << "목표 원소 6의 인덱스 = " << index << endl;
+
+ return 0;
+}
\ No newline at end of file
diff --git a/ko/codes/cpp/chapter_divide_and_conquer/build_tree.cpp b/ko/codes/cpp/chapter_divide_and_conquer/build_tree.cpp
new file mode 100644
index 000000000..e745a277c
--- /dev/null
+++ b/ko/codes/cpp/chapter_divide_and_conquer/build_tree.cpp
@@ -0,0 +1,51 @@
+/**
+ * File: build_tree.cpp
+ * Created Time: 2023-07-17
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 이진 트리 구성: 분할 정복 */
+TreeNode *dfs(vector &preorder, unordered_map &inorderMap, int i, int l, int r) {
+ // 서브트리 구간이 비어 있으면 종료한다
+ if (r - l < 0)
+ return NULL;
+ // 루트 노드를 초기화한다
+ TreeNode *root = new TreeNode(preorder[i]);
+ // m을 조회하여 왼쪽과 오른쪽 서브트리를 나눈다
+ int m = inorderMap[preorder[i]];
+ // 부분 문제: 왼쪽 서브트리 구성
+ root->left = dfs(preorder, inorderMap, i + 1, l, m - 1);
+ // 부분 문제: 오른쪽 서브트리 구성
+ root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r);
+ // 루트 노드를 반환한다
+ return root;
+}
+
+/* 이진 트리 구성 */
+TreeNode *buildTree(vector &preorder, vector &inorder) {
+ // inorder 원소와 인덱스의 대응 관계를 저장할 해시 테이블을 초기화한다
+ unordered_map inorderMap;
+ for (int i = 0; i < inorder.size(); i++) {
+ inorderMap[inorder[i]] = i;
+ }
+ TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1);
+ return root;
+}
+
+/* 실행 코드 */
+int main() {
+ vector preorder = {3, 9, 2, 1, 7};
+ vector inorder = {9, 3, 1, 2, 7};
+ cout << "전위 순회 = ";
+ printVector(preorder);
+ cout << "중위 순회 = ";
+ printVector(inorder);
+
+ TreeNode *root = buildTree(preorder, inorder);
+ cout << "구성한 이진 트리:\n";
+ printTree(root);
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_divide_and_conquer/fast_power.cpp b/ko/codes/cpp/chapter_divide_and_conquer/fast_power.cpp
new file mode 100644
index 000000000..ae293ff79
--- /dev/null
+++ b/ko/codes/cpp/chapter_divide_and_conquer/fast_power.cpp
@@ -0,0 +1,26 @@
+/**
+ * File: fast_power.cpp
+ * Created Time: 2026-08-18
+ * Author: Hello Algo Team
+ */
+
+#include
+
+/* 빠른 거듭제곱 */
+int fastPow(int x, int n) {
+ if (n == 0) {
+ return 1;
+ }
+ int half = fastPow(x, n / 2);
+ if (n % 2 == 0) {
+ return half * half;
+ }
+ return half * half * x;
+}
+
+int main() {
+ assert(fastPow(7, 0) == 1);
+ assert(fastPow(3, 5) == 243);
+ assert(fastPow(2, 6) == 64);
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_divide_and_conquer/hanota.cpp b/ko/codes/cpp/chapter_divide_and_conquer/hanota.cpp
new file mode 100644
index 000000000..4b5560921
--- /dev/null
+++ b/ko/codes/cpp/chapter_divide_and_conquer/hanota.cpp
@@ -0,0 +1,66 @@
+/**
+ * File: hanota.cpp
+ * Created Time: 2023-07-17
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 원판 하나 이동 */
+void move(vector &src, vector &tar) {
+ // src의 맨 위에서 원판 하나를 꺼낸다
+ int pan = src.back();
+ src.pop_back();
+ // 원판을 tar의 맨 위에 놓는다
+ tar.push_back(pan);
+}
+
+/* 하노이의 탑 문제 f(i) 풀기 */
+void dfs(int i, vector &src, vector &buf, vector &tar) {
+ // src에 원판이 하나만 남아 있으면 바로 tar로 옮긴다
+ if (i == 1) {
+ move(src, tar);
+ return;
+ }
+ // 부분 문제 f(i-1): tar를 이용하여 src의 맨 위 원판 i-1개를 buf로 옮긴다
+ dfs(i - 1, src, tar, buf);
+ // 부분 문제 f(1): src에 남은 원판 하나를 tar로 옮긴다
+ move(src, tar);
+ // 부분 문제 f(i-1): src를 이용하여 buf의 맨 위 원판 i-1개를 tar로 옮긴다
+ dfs(i - 1, buf, src, tar);
+}
+
+/* 하노이의 탑 문제 풀기 */
+void solveHanota(vector &A, vector &B, vector &C) {
+ int n = A.size();
+ // B를 이용하여 A의 맨 위 원판 n개를 C로 옮긴다
+ dfs(n, A, B, C);
+}
+
+/* 실행 코드 */
+int main() {
+ // 리스트의 맨 뒤가 기둥의 맨 위이다
+ vector A = {5, 4, 3, 2, 1};
+ vector B = {};
+ vector C = {};
+
+ cout << "초기 상태:\n";
+ cout << "A =";
+ printVector(A);
+ cout << "B =";
+ printVector(B);
+ cout << "C =";
+ printVector(C);
+
+ solveHanota(A, B, C);
+
+ cout << "원판 이동을 마친 후:\n";
+ cout << "A =";
+ printVector(A);
+ cout << "B =";
+ printVector(B);
+ cout << "C =";
+ printVector(C);
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_dynamic_programming/CMakeLists.txt b/ko/codes/cpp/chapter_dynamic_programming/CMakeLists.txt
new file mode 100644
index 000000000..ed185458a
--- /dev/null
+++ b/ko/codes/cpp/chapter_dynamic_programming/CMakeLists.txt
@@ -0,0 +1,10 @@
+add_executable(climbing_stairs_backtrack climbing_stairs_backtrack.cpp)
+add_executable(climbing_stairs_dfs climbing_stairs_dfs.cpp)
+add_executable(climbing_stairs_dfs_mem climbing_stairs_dfs_mem.cpp)
+add_executable(climbing_stairs_dp climbing_stairs_dp.cpp)
+add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.cpp)
+add_executable(min_path_sum min_path_sum.cpp)
+add_executable(unbounded_knapsack unbounded_knapsack.cpp)
+add_executable(coin_change coin_change.cpp)
+add_executable(coin_change_ii coin_change_ii.cpp)
+add_executable(edit_distance edit_distance.cpp)
\ No newline at end of file
diff --git a/ko/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp b/ko/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp
new file mode 100644
index 000000000..20b03d058
--- /dev/null
+++ b/ko/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp
@@ -0,0 +1,43 @@
+
+/**
+ * File: climbing_stairs_backtrack.cpp
+ * Created Time: 2023-06-30
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 백트래킹 */
+void backtrack(vector &choices, int state, int n, vector &res) {
+ // n번째 계단에 도달하면 방법의 수에 1을 더한다
+ if (state == n)
+ res[0]++;
+ // 모든 선택지를 순회한다
+ for (auto &choice : choices) {
+ // 가지치기: n번째 계단을 넘어갈 수 없다
+ if (state + choice > n)
+ continue;
+ // 시도: 선택하고 상태를 갱신한다
+ backtrack(choices, state + choice, n, res);
+ // 되돌리기
+ }
+}
+
+/* 계단 오르기: 백트래킹 */
+int climbingStairsBacktrack(int n) {
+ vector choices = {1, 2}; // 한 계단 또는 두 계단 오르기를 선택할 수 있다
+ int state = 0; // 0번째 계단에서 오르기 시작한다
+ vector res = {0}; // res[0]에 방법의 수를 기록한다
+ backtrack(choices, state, n, res);
+ return res[0];
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 9;
+
+ int res = climbingStairsBacktrack(n);
+ cout << "계단 수: " << n << " 칸, 오르는 방법은 총 " << res << " 가지이다" << endl;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp b/ko/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp
new file mode 100644
index 000000000..1841340f3
--- /dev/null
+++ b/ko/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp
@@ -0,0 +1,37 @@
+/**
+ * File: climbing_stairs_constraint_dp.cpp
+ * Created Time: 2023-07-01
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 제약 조건이 있는 계단 오르기: 동적 계획법 */
+int climbingStairsConstraintDP(int n) {
+ if (n == 1 || n == 2) {
+ return 1;
+ }
+ // 부분 문제의 해를 저장할 dp 테이블을 초기화한다
+ vector> dp(n + 1, vector(3, 0));
+ // 초기 상태: 가장 작은 부분 문제의 해를 미리 설정한다
+ dp[1][1] = 1;
+ dp[1][2] = 0;
+ dp[2][1] = 0;
+ dp[2][2] = 1;
+ // 상태 전이: 작은 부분 문제부터 점차 큰 부분 문제를 푼다
+ for (int i = 3; i <= n; i++) {
+ dp[i][1] = dp[i - 1][2];
+ dp[i][2] = dp[i - 2][1] + dp[i - 2][2];
+ }
+ return dp[n][1] + dp[n][2];
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 9;
+
+ int res = climbingStairsConstraintDP(n);
+ cout << "계단 수: " << n << " 칸, 오르는 방법은 총 " << res << " 가지이다" << endl;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp b/ko/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp
new file mode 100644
index 000000000..667bf5fee
--- /dev/null
+++ b/ko/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp
@@ -0,0 +1,32 @@
+/**
+ * File: climbing_stairs_dfs.cpp
+ * Created Time: 2023-06-30
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 탐색 */
+int dfs(int i) {
+ // dp[1]과 dp[2]는 이미 알고 있으므로 해당 값을 반환한다
+ if (i == 1 || i == 2)
+ return i;
+ // dp[i] = dp[i-1] + dp[i-2]
+ int count = dfs(i - 1) + dfs(i - 2);
+ return count;
+}
+
+/* 계단 오르기: 탐색 */
+int climbingStairsDFS(int n) {
+ return dfs(n);
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 9;
+
+ int res = climbingStairsDFS(n);
+ cout << "계단 수: " << n << " 칸, 오르는 방법은 총 " << res << " 가지이다" << endl;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp b/ko/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp
new file mode 100644
index 000000000..6355d49ef
--- /dev/null
+++ b/ko/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp
@@ -0,0 +1,39 @@
+/**
+ * File: climbing_stairs_dfs_mem.cpp
+ * Created Time: 2023-06-30
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 메모이제이션을 이용한 탐색 */
+int dfs(int i, vector &mem) {
+ // dp[1]과 dp[2]는 이미 알고 있으므로 해당 값을 반환한다
+ if (i == 1 || i == 2)
+ return i;
+ // dp[i]의 기록이 있으면 해당 값을 바로 반환한다
+ if (mem[i] != -1)
+ return mem[i];
+ // dp[i] = dp[i-1] + dp[i-2]
+ int count = dfs(i - 1, mem) + dfs(i - 2, mem);
+ // dp[i]를 기록한다
+ mem[i] = count;
+ return count;
+}
+
+/* 계단 오르기: 메모이제이션을 이용한 탐색 */
+int climbingStairsDFSMem(int n) {
+ // mem[i]는 i번째 계단까지 오르는 전체 방법의 수를 기록하며, -1은 기록이 없음을 나타낸다
+ vector mem(n + 1, -1);
+ return dfs(n, mem);
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 9;
+
+ int res = climbingStairsDFSMem(n);
+ cout << "계단 수: " << n << " 칸, 오르는 방법은 총 " << res << " 가지이다" << endl;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp b/ko/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp
new file mode 100644
index 000000000..c7e21483d
--- /dev/null
+++ b/ko/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp
@@ -0,0 +1,49 @@
+/**
+ * File: climbing_stairs_dp.cpp
+ * Created Time: 2023-06-30
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 계단 오르기: 동적 계획법 */
+int climbingStairsDP(int n) {
+ if (n == 1 || n == 2)
+ return n;
+ // 부분 문제의 해를 저장할 dp 테이블을 초기화한다
+ vector dp(n + 1);
+ // 초기 상태: 가장 작은 부분 문제의 해를 미리 설정한다
+ dp[1] = 1;
+ dp[2] = 2;
+ // 상태 전이: 작은 부분 문제부터 점차 큰 부분 문제를 푼다
+ for (int i = 3; i <= n; i++) {
+ dp[i] = dp[i - 1] + dp[i - 2];
+ }
+ return dp[n];
+}
+
+/* 계단 오르기: 공간을 최적화한 동적 계획법 */
+int climbingStairsDPComp(int n) {
+ if (n == 1 || n == 2)
+ return n;
+ int a = 1, b = 2;
+ for (int i = 3; i <= n; i++) {
+ int tmp = b;
+ b = a + b;
+ a = tmp;
+ }
+ return b;
+}
+
+/* 실행 코드 */
+int main() {
+ int n = 9;
+
+ int res = climbingStairsDP(n);
+ cout << "계단 수: " << n << " 칸, 오르는 방법은 총 " << res << " 가지이다" << endl;
+
+ res = climbingStairsDPComp(n);
+ cout << "계단 수: " << n << " 칸, 오르는 방법은 총 " << res << " 가지이다" << endl;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_dynamic_programming/coin_change.cpp b/ko/codes/cpp/chapter_dynamic_programming/coin_change.cpp
new file mode 100644
index 000000000..f681922b5
--- /dev/null
+++ b/ko/codes/cpp/chapter_dynamic_programming/coin_change.cpp
@@ -0,0 +1,70 @@
+/**
+ * File: coin_change.cpp
+ * Created Time: 2023-07-11
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 동전 교환: 동적 계획법 */
+int coinChangeDP(vector &coins, int amt) {
+ int n = coins.size();
+ int MAX = amt + 1;
+ // dp 테이블을 초기화한다
+ vector> dp(n + 1, vector(amt + 1, 0));
+ // 상태 전이: 첫 번째 행과 첫 번째 열
+ for (int a = 1; a <= amt; a++) {
+ dp[0][a] = MAX;
+ }
+ // 상태 전이: 나머지 행과 열
+ for (int i = 1; i <= n; i++) {
+ for (int a = 1; a <= amt; a++) {
+ if (coins[i - 1] > a) {
+ // 목표 금액을 초과하면 동전 i를 선택하지 않는다
+ dp[i][a] = dp[i - 1][a];
+ } else {
+ // 동전 i를 선택하지 않는 경우와 선택하는 경우 중 더 작은 값
+ dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1);
+ }
+ }
+ }
+ return dp[n][amt] != MAX ? dp[n][amt] : -1;
+}
+
+/* 동전 교환: 공간을 최적화한 동적 계획법 */
+int coinChangeDPComp(vector &coins, int amt) {
+ int n = coins.size();
+ int MAX = amt + 1;
+ // dp 테이블을 초기화한다
+ vector dp(amt + 1, MAX);
+ dp[0] = 0;
+ // 상태 전이
+ for (int i = 1; i <= n; i++) {
+ for (int a = 1; a <= amt; a++) {
+ if (coins[i - 1] > a) {
+ // 목표 금액을 초과하면 동전 i를 선택하지 않는다
+ dp[a] = dp[a];
+ } else {
+ // 동전 i를 선택하지 않는 경우와 선택하는 경우 중 더 작은 값
+ dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1);
+ }
+ }
+ }
+ return dp[amt] != MAX ? dp[amt] : -1;
+}
+
+/* 실행 코드 */
+int main() {
+ vector coins = {1, 2, 5};
+ int amt = 4;
+
+ // 동적 계획법
+ int res = coinChangeDP(coins, amt);
+ cout << "목표 금액을 만드는 데 필요한 최소 동전 개수: " << res << endl;
+
+ // 공간을 최적화한 동적 계획법
+ res = coinChangeDPComp(coins, amt);
+ cout << "목표 금액을 만드는 데 필요한 최소 동전 개수: " << res << endl;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp b/ko/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp
new file mode 100644
index 000000000..aebac78f1
--- /dev/null
+++ b/ko/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp
@@ -0,0 +1,68 @@
+/**
+ * File: coin_change_ii.cpp
+ * Created Time: 2023-07-11
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 동전 교환 II: 동적 계획법 */
+int coinChangeIIDP(vector &coins, int amt) {
+ int n = coins.size();
+ // dp 테이블을 초기화한다
+ vector> dp(n + 1, vector(amt + 1, 0));
+ // 첫 번째 열을 초기화한다
+ for (int i = 0; i <= n; i++) {
+ dp[i][0] = 1;
+ }
+ // 상태 전이
+ for (int i = 1; i <= n; i++) {
+ for (int a = 1; a <= amt; a++) {
+ if (coins[i - 1] > a) {
+ // 목표 금액을 초과하면 동전 i를 선택하지 않는다
+ dp[i][a] = dp[i - 1][a];
+ } else {
+ // 동전 i를 선택하지 않는 경우와 선택하는 경우의 방법 수를 더한다
+ dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]];
+ }
+ }
+ }
+ return dp[n][amt];
+}
+
+/* 동전 교환 II: 공간을 최적화한 동적 계획법 */
+int coinChangeIIDPComp(vector &coins, int amt) {
+ int n = coins.size();
+ // dp 테이블을 초기화한다
+ vector dp(amt + 1, 0);
+ dp[0] = 1;
+ // 상태 전이
+ for (int i = 1; i <= n; i++) {
+ for (int a = 1; a <= amt; a++) {
+ if (coins[i - 1] > a) {
+ // 목표 금액을 초과하면 동전 i를 선택하지 않는다
+ dp[a] = dp[a];
+ } else {
+ // 동전 i를 선택하지 않는 경우와 선택하는 경우의 방법 수를 더한다
+ dp[a] = dp[a] + dp[a - coins[i - 1]];
+ }
+ }
+ }
+ return dp[amt];
+}
+
+/* 실행 코드 */
+int main() {
+ vector coins = {1, 2, 5};
+ int amt = 5;
+
+ // 동적 계획법
+ int res = coinChangeIIDP(coins, amt);
+ cout << "목표 금액을 만드는 동전 조합의 수: " << res << endl;
+
+ // 공간을 최적화한 동적 계획법
+ res = coinChangeIIDPComp(coins, amt);
+ cout << "목표 금액을 만드는 동전 조합의 수: " << res << endl;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_dynamic_programming/edit_distance.cpp b/ko/codes/cpp/chapter_dynamic_programming/edit_distance.cpp
new file mode 100644
index 000000000..02f016dbe
--- /dev/null
+++ b/ko/codes/cpp/chapter_dynamic_programming/edit_distance.cpp
@@ -0,0 +1,136 @@
+/**
+ * File: edit_distance.cpp
+ * Created Time: 2023-07-13
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 편집 거리: 완전 탐색 */
+int editDistanceDFS(string s, string t, int i, int j) {
+ // s와 t가 모두 비어 있으면 0을 반환한다
+ if (i == 0 && j == 0)
+ return 0;
+ // s가 비어 있으면 t의 길이를 반환한다
+ if (i == 0)
+ return j;
+ // t가 비어 있으면 s의 길이를 반환한다
+ if (j == 0)
+ return i;
+ // 두 문자가 같으면 두 문자를 바로 건너뛴다
+ if (s[i - 1] == t[j - 1])
+ return editDistanceDFS(s, t, i - 1, j - 1);
+ // 최소 편집 횟수 = 삽입, 삭제, 교체 세 연산에 대한 최소 편집 횟수 + 1
+ int insert = editDistanceDFS(s, t, i, j - 1);
+ int del = editDistanceDFS(s, t, i - 1, j);
+ int replace = editDistanceDFS(s, t, i - 1, j - 1);
+ // 최소 편집 횟수를 반환한다
+ return min(min(insert, del), replace) + 1;
+}
+
+/* 편집 거리: 메모이제이션을 이용한 탐색 */
+int editDistanceDFSMem(string s, string t, vector> &mem, int i, int j) {
+ // s와 t가 모두 비어 있으면 0을 반환한다
+ if (i == 0 && j == 0)
+ return 0;
+ // s가 비어 있으면 t의 길이를 반환한다
+ if (i == 0)
+ return j;
+ // t가 비어 있으면 s의 길이를 반환한다
+ if (j == 0)
+ return i;
+ // 기록이 이미 있으면 해당 값을 바로 반환한다
+ if (mem[i][j] != -1)
+ return mem[i][j];
+ // 두 문자가 같으면 두 문자를 바로 건너뛴다
+ if (s[i - 1] == t[j - 1])
+ return editDistanceDFSMem(s, t, mem, i - 1, j - 1);
+ // 최소 편집 횟수 = 삽입, 삭제, 교체 세 연산에 대한 최소 편집 횟수 + 1
+ int insert = editDistanceDFSMem(s, t, mem, i, j - 1);
+ int del = editDistanceDFSMem(s, t, mem, i - 1, j);
+ int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1);
+ // 최소 편집 횟수를 기록하고 반환한다
+ mem[i][j] = min(min(insert, del), replace) + 1;
+ return mem[i][j];
+}
+
+/* 편집 거리: 동적 계획법 */
+int editDistanceDP(string s, string t) {
+ int n = s.length(), m = t.length();
+ vector> dp(n + 1, vector(m + 1, 0));
+ // 상태 전이: 첫 번째 행과 첫 번째 열
+ for (int i = 1; i <= n; i++) {
+ dp[i][0] = i;
+ }
+ for (int j = 1; j <= m; j++) {
+ dp[0][j] = j;
+ }
+ // 상태 전이: 나머지 행과 열
+ for (int i = 1; i <= n; i++) {
+ for (int j = 1; j <= m; j++) {
+ if (s[i - 1] == t[j - 1]) {
+ // 두 문자가 같으면 두 문자를 바로 건너뛴다
+ dp[i][j] = dp[i - 1][j - 1];
+ } else {
+ // 최소 편집 횟수 = 삽입, 삭제, 교체 세 연산에 대한 최소 편집 횟수 + 1
+ dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1;
+ }
+ }
+ }
+ return dp[n][m];
+}
+
+/* 편집 거리: 공간을 최적화한 동적 계획법 */
+int editDistanceDPComp(string s, string t) {
+ int n = s.length(), m = t.length();
+ vector dp(m + 1, 0);
+ // 상태 전이: 첫 번째 행
+ for (int j = 1; j <= m; j++) {
+ dp[j] = j;
+ }
+ // 상태 전이: 나머지 행
+ for (int i = 1; i <= n; i++) {
+ // 상태 전이: 첫 번째 열
+ int leftup = dp[0]; // dp[i-1, j-1]을 임시로 저장한다
+ dp[0] = i;
+ // 상태 전이: 나머지 열
+ for (int j = 1; j <= m; j++) {
+ int temp = dp[j];
+ if (s[i - 1] == t[j - 1]) {
+ // 두 문자가 같으면 두 문자를 바로 건너뛴다
+ dp[j] = leftup;
+ } else {
+ // 최소 편집 횟수 = 삽입, 삭제, 교체 세 연산에 대한 최소 편집 횟수 + 1
+ dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1;
+ }
+ leftup = temp; // 다음 반복의 dp[i-1, j-1]로 갱신한다
+ }
+ }
+ return dp[m];
+}
+
+/* 실행 코드 */
+int main() {
+ string s = "bag";
+ string t = "pack";
+ int n = s.length(), m = t.length();
+
+ // 완전 탐색
+ int res = editDistanceDFS(s, t, n, m);
+ cout << "변경: " << s << " → " << t << " 변경에 필요한 최소 편집 횟수: " << res << " 회\n";
+
+ // 메모이제이션을 이용한 탐색
+ vector> mem(n + 1, vector(m + 1, -1));
+ res = editDistanceDFSMem(s, t, mem, n, m);
+ cout << "변경: " << s << " → " << t << " 변경에 필요한 최소 편집 횟수: " << res << " 회\n";
+
+ // 동적 계획법
+ res = editDistanceDP(s, t);
+ cout << "변경: " << s << " → " << t << " 변경에 필요한 최소 편집 횟수: " << res << " 회\n";
+
+ // 공간을 최적화한 동적 계획법
+ res = editDistanceDPComp(s, t);
+ cout << "변경: " << s << " → " << t << " 변경에 필요한 최소 편집 횟수: " << res << " 회\n";
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_dynamic_programming/knapsack.cpp b/ko/codes/cpp/chapter_dynamic_programming/knapsack.cpp
new file mode 100644
index 000000000..dbdaa6a73
--- /dev/null
+++ b/ko/codes/cpp/chapter_dynamic_programming/knapsack.cpp
@@ -0,0 +1,109 @@
+#include
+#include
+#include
+
+using namespace std;
+
+/* 0-1 배낭: 완전 탐색 */
+int knapsackDFS(vector &wgt, vector &val, int i, int c) {
+ // 모든 물건의 선택을 마쳤거나 배낭에 남은 용량이 없으면 가치 0을 반환한다
+ if (i == 0 || c == 0) {
+ return 0;
+ }
+ // 배낭 용량을 초과하면 배낭에 넣지 않는 선택만 가능하다
+ if (wgt[i - 1] > c) {
+ return knapsackDFS(wgt, val, i - 1, c);
+ }
+ // 물건 i를 넣지 않는 경우와 넣는 경우의 최대 가치를 계산한다
+ int no = knapsackDFS(wgt, val, i - 1, c);
+ int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1];
+ // 두 경우 중 더 큰 가치를 반환한다
+ return max(no, yes);
+}
+
+/* 0-1 배낭: 메모이제이션을 이용한 탐색 */
+int knapsackDFSMem(vector &wgt, vector &val, vector> &mem, int i, int c) {
+ // 모든 물건의 선택을 마쳤거나 배낭에 남은 용량이 없으면 가치 0을 반환한다
+ if (i == 0 || c == 0) {
+ return 0;
+ }
+ // 기록이 이미 있으면 바로 반환한다
+ if (mem[i][c] != -1) {
+ return mem[i][c];
+ }
+ // 배낭 용량을 초과하면 배낭에 넣지 않는 선택만 가능하다
+ if (wgt[i - 1] > c) {
+ return knapsackDFSMem(wgt, val, mem, i - 1, c);
+ }
+ // 물건 i를 넣지 않는 경우와 넣는 경우의 최대 가치를 계산한다
+ int no = knapsackDFSMem(wgt, val, mem, i - 1, c);
+ int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1];
+ // 두 경우 중 더 큰 가치를 기록하고 반환한다
+ mem[i][c] = max(no, yes);
+ return mem[i][c];
+}
+
+/* 0-1 배낭: 동적 계획법 */
+int knapsackDP(vector &wgt, vector &val, int cap) {
+ int n = wgt.size();
+ // dp 테이블을 초기화한다
+ vector> dp(n + 1, vector(cap + 1, 0));
+ // 상태 전이
+ for (int i = 1; i <= n; i++) {
+ for (int c = 1; c <= cap; c++) {
+ if (wgt[i - 1] > c) {
+ // 배낭 용량을 초과하면 물건 i를 선택하지 않는다
+ dp[i][c] = dp[i - 1][c];
+ } else {
+ // 물건 i를 선택하지 않는 경우와 선택하는 경우 중 더 큰 값
+ dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]);
+ }
+ }
+ }
+ return dp[n][cap];
+}
+
+/* 0-1 배낭: 공간을 최적화한 동적 계획법 */
+int knapsackDPComp(vector &wgt, vector &val, int cap) {
+ int n = wgt.size();
+ // dp 테이블을 초기화한다
+ vector dp(cap + 1, 0);
+ // 상태 전이
+ for (int i = 1; i <= n; i++) {
+ // 역방향 순회
+ for (int c = cap; c >= 1; c--) {
+ if (wgt[i - 1] <= c) {
+ // 물건 i를 선택하지 않는 경우와 선택하는 경우 중 더 큰 값
+ dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);
+ }
+ }
+ }
+ return dp[cap];
+}
+
+/* 실행 코드 */
+int main() {
+ vector wgt = {10, 20, 30, 40, 50};
+ vector val = {50, 120, 150, 210, 240};
+ int cap = 50;
+ int n = wgt.size();
+
+ // 완전 탐색
+ int res = knapsackDFS(wgt, val, n, cap);
+ cout << "배낭 용량을 초과하지 않는 물건의 최대 가치: " << res << endl;
+
+ // 메모이제이션을 이용한 탐색
+ vector> mem(n + 1, vector(cap + 1, -1));
+ res = knapsackDFSMem(wgt, val, mem, n, cap);
+ cout << "배낭 용량을 초과하지 않는 물건의 최대 가치: " << res << endl;
+
+ // 동적 계획법
+ res = knapsackDP(wgt, val, cap);
+ cout << "배낭 용량을 초과하지 않는 물건의 최대 가치: " << res << endl;
+
+ // 공간을 최적화한 동적 계획법
+ res = knapsackDPComp(wgt, val, cap);
+ cout << "배낭 용량을 초과하지 않는 물건의 최대 가치: " << res << endl;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp b/ko/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp
new file mode 100644
index 000000000..3717b89a6
--- /dev/null
+++ b/ko/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp
@@ -0,0 +1,53 @@
+/**
+ * File: min_cost_climbing_stairs_dp.cpp
+ * Created Time: 2023-06-30
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 계단 오르기의 최소 비용: 동적 계획법 */
+int minCostClimbingStairsDP(vector &cost) {
+ int n = cost.size() - 1;
+ if (n == 1 || n == 2)
+ return cost[n];
+ // 부분 문제의 해를 저장할 dp 테이블을 초기화한다
+ vector dp(n + 1);
+ // 초기 상태: 가장 작은 부분 문제의 해를 미리 설정한다
+ dp[1] = cost[1];
+ dp[2] = cost[2];
+ // 상태 전이: 작은 부분 문제부터 점차 큰 부분 문제를 푼다
+ for (int i = 3; i <= n; i++) {
+ dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];
+ }
+ return dp[n];
+}
+
+/* 계단 오르기의 최소 비용: 공간을 최적화한 동적 계획법 */
+int minCostClimbingStairsDPComp(vector &cost) {
+ int n = cost.size() - 1;
+ if (n == 1 || n == 2)
+ return cost[n];
+ int a = cost[1], b = cost[2];
+ for (int i = 3; i <= n; i++) {
+ int tmp = b;
+ b = min(a, tmp) + cost[i];
+ a = tmp;
+ }
+ return b;
+}
+
+/* 실행 코드 */
+int main() {
+ vector cost = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1};
+ cout << "입력한 계단의 비용 리스트: ";
+ printVector(cost);
+
+ int res = minCostClimbingStairsDP(cost);
+ cout << "계단을 끝까지 오르는 최소 비용: " << res << endl;
+
+ res = minCostClimbingStairsDPComp(cost);
+ cout << "계단을 끝까지 오르는 최소 비용: " << res << endl;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp b/ko/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp
new file mode 100644
index 000000000..711106d5e
--- /dev/null
+++ b/ko/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp
@@ -0,0 +1,116 @@
+/**
+ * File: min_path_sum.cpp
+ * Created Time: 2023-07-10
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 최소 경로 합: 완전 탐색 */
+int minPathSumDFS(vector> &grid, int i, int j) {
+ // 왼쪽 위 모서리의 칸이면 탐색을 종료한다
+ if (i == 0 && j == 0) {
+ return grid[0][0];
+ }
+ // 행 또는 열 인덱스가 범위를 벗어나면 비용 +∞를 반환한다
+ if (i < 0 || j < 0) {
+ return INT_MAX;
+ }
+ // 왼쪽 위 모서리에서 (i-1, j)와 (i, j-1)까지의 최소 경로 비용을 계산한다
+ int up = minPathSumDFS(grid, i - 1, j);
+ int left = minPathSumDFS(grid, i, j - 1);
+ // 왼쪽 위 모서리에서 (i, j)까지의 최소 경로 비용을 반환한다
+ return min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX;
+}
+
+/* 최소 경로 합: 메모이제이션을 이용한 탐색 */
+int minPathSumDFSMem(vector> &grid, vector> &mem, int i, int j) {
+ // 왼쪽 위 모서리의 칸이면 탐색을 종료한다
+ if (i == 0 && j == 0) {
+ return grid[0][0];
+ }
+ // 행 또는 열 인덱스가 범위를 벗어나면 비용 +∞를 반환한다
+ if (i < 0 || j < 0) {
+ return INT_MAX;
+ }
+ // 기록이 이미 있으면 바로 반환한다
+ if (mem[i][j] != -1) {
+ return mem[i][j];
+ }
+ // 왼쪽 칸과 위쪽 칸까지의 최소 경로 비용
+ int up = minPathSumDFSMem(grid, mem, i - 1, j);
+ int left = minPathSumDFSMem(grid, mem, i, j - 1);
+ // 왼쪽 위 모서리에서 (i, j)까지의 최소 경로 비용을 기록하고 반환한다
+ mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX;
+ return mem[i][j];
+}
+
+/* 최소 경로 합: 동적 계획법 */
+int minPathSumDP(vector> &grid) {
+ int n = grid.size(), m = grid[0].size();
+ // dp 테이블을 초기화한다
+ vector> dp(n, vector(m));
+ dp[0][0] = grid[0][0];
+ // 상태 전이: 첫 번째 행
+ for (int j = 1; j < m; j++) {
+ dp[0][j] = dp[0][j - 1] + grid[0][j];
+ }
+ // 상태 전이: 첫 번째 열
+ for (int i = 1; i < n; i++) {
+ dp[i][0] = dp[i - 1][0] + grid[i][0];
+ }
+ // 상태 전이: 나머지 행과 열
+ for (int i = 1; i < n; i++) {
+ for (int j = 1; j < m; j++) {
+ dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j];
+ }
+ }
+ return dp[n - 1][m - 1];
+}
+
+/* 최소 경로 합: 공간을 최적화한 동적 계획법 */
+int minPathSumDPComp(vector> &grid) {
+ int n = grid.size(), m = grid[0].size();
+ // dp 테이블을 초기화한다
+ vector dp(m);
+ // 상태 전이: 첫 번째 행
+ dp[0] = grid[0][0];
+ for (int j = 1; j < m; j++) {
+ dp[j] = dp[j - 1] + grid[0][j];
+ }
+ // 상태 전이: 나머지 행
+ for (int i = 1; i < n; i++) {
+ // 상태 전이: 첫 번째 열
+ dp[0] = dp[0] + grid[i][0];
+ // 상태 전이: 나머지 열
+ for (int j = 1; j < m; j++) {
+ dp[j] = min(dp[j - 1], dp[j]) + grid[i][j];
+ }
+ }
+ return dp[m - 1];
+}
+
+/* 실행 코드 */
+int main() {
+ vector> grid = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}};
+ int n = grid.size(), m = grid[0].size();
+
+ // 완전 탐색
+ int res = minPathSumDFS(grid, n - 1, m - 1);
+ cout << "왼쪽 위에서 오른쪽 아래까지의 최소 경로 합: " << res << endl;
+
+ // 메모이제이션을 이용한 탐색
+ vector> mem(n, vector(m, -1));
+ res = minPathSumDFSMem(grid, mem, n - 1, m - 1);
+ cout << "왼쪽 위에서 오른쪽 아래까지의 최소 경로 합: " << res << endl;
+
+ // 동적 계획법
+ res = minPathSumDP(grid);
+ cout << "왼쪽 위에서 오른쪽 아래까지의 최소 경로 합: " << res << endl;
+
+ // 공간을 최적화한 동적 계획법
+ res = minPathSumDPComp(grid);
+ cout << "왼쪽 위에서 오른쪽 아래까지의 최소 경로 합: " << res << endl;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp b/ko/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp
new file mode 100644
index 000000000..9b26e87ef
--- /dev/null
+++ b/ko/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp
@@ -0,0 +1,64 @@
+/**
+ * File: unbounded_knapsack.cpp
+ * Created Time: 2023-07-11
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 무한 배낭: 동적 계획법 */
+int unboundedKnapsackDP(vector &wgt, vector &val, int cap) {
+ int n = wgt.size();
+ // dp 테이블을 초기화한다
+ vector> dp(n + 1, vector(cap + 1, 0));
+ // 상태 전이
+ for (int i = 1; i <= n; i++) {
+ for (int c = 1; c <= cap; c++) {
+ if (wgt[i - 1] > c) {
+ // 배낭 용량을 초과하면 물건 i를 선택하지 않는다
+ dp[i][c] = dp[i - 1][c];
+ } else {
+ // 물건 i를 선택하지 않는 경우와 선택하는 경우 중 더 큰 값
+ dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]);
+ }
+ }
+ }
+ return dp[n][cap];
+}
+
+/* 무한 배낭: 공간을 최적화한 동적 계획법 */
+int unboundedKnapsackDPComp(vector &wgt, vector &val, int cap) {
+ int n = wgt.size();
+ // dp 테이블을 초기화한다
+ vector dp(cap + 1, 0);
+ // 상태 전이
+ for (int i = 1; i <= n; i++) {
+ for (int c = 1; c <= cap; c++) {
+ if (wgt[i - 1] > c) {
+ // 배낭 용량을 초과하면 물건 i를 선택하지 않는다
+ dp[c] = dp[c];
+ } else {
+ // 물건 i를 선택하지 않는 경우와 선택하는 경우 중 더 큰 값
+ dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]);
+ }
+ }
+ }
+ return dp[cap];
+}
+
+/* 실행 코드 */
+int main() {
+ vector wgt = {1, 2, 3};
+ vector val = {5, 11, 15};
+ int cap = 4;
+
+ // 동적 계획법
+ int res = unboundedKnapsackDP(wgt, val, cap);
+ cout << "배낭 용량을 초과하지 않는 물건의 최대 가치: " << res << endl;
+
+ // 공간을 최적화한 동적 계획법
+ res = unboundedKnapsackDPComp(wgt, val, cap);
+ cout << "배낭 용량을 초과하지 않는 물건의 최대 가치: " << res << endl;
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_graph/CMakeLists.txt b/ko/codes/cpp/chapter_graph/CMakeLists.txt
new file mode 100644
index 000000000..4a56ce35b
--- /dev/null
+++ b/ko/codes/cpp/chapter_graph/CMakeLists.txt
@@ -0,0 +1,5 @@
+add_executable(graph_bfs graph_bfs.cpp)
+add_executable(graph_dfs graph_dfs.cpp)
+# add_executable(graph_adjacency_list graph_adjacency_list.cpp)
+add_executable(graph_adjacency_list_test graph_adjacency_list_test.cpp)
+add_executable(graph_adjacency_matrix graph_adjacency_matrix.cpp)
diff --git a/ko/codes/cpp/chapter_graph/graph_adjacency_list.cpp b/ko/codes/cpp/chapter_graph/graph_adjacency_list.cpp
new file mode 100644
index 000000000..b134ced20
--- /dev/null
+++ b/ko/codes/cpp/chapter_graph/graph_adjacency_list.cpp
@@ -0,0 +1,90 @@
+/**
+ * File: graph_adjacency_list.cpp
+ * Created Time: 2023-02-09
+ * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+
+/* 인접 리스트로 구현한 무방향 그래프 클래스 */
+class GraphAdjList {
+ public:
+ // 인접 리스트의 key: 정점, value: 해당 정점의 모든 인접 정점
+ unordered_map> adjList;
+
+ /* vector에서 지정한 노드 삭제 */
+ void remove(vector &vec, Vertex *vet) {
+ for (int i = 0; i < vec.size(); i++) {
+ if (vec[i] == vet) {
+ vec.erase(vec.begin() + i);
+ break;
+ }
+ }
+ }
+
+ /* 생성자 */
+ GraphAdjList(const vector> &edges) {
+ // 모든 정점과 간선을 추가한다
+ for (const vector &edge : edges) {
+ addVertex(edge[0]);
+ addVertex(edge[1]);
+ addEdge(edge[0], edge[1]);
+ }
+ }
+
+ /* 정점 개수 반환 */
+ int size() {
+ return adjList.size();
+ }
+
+ /* 간선 추가 */
+ void addEdge(Vertex *vet1, Vertex *vet2) {
+ if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)
+ throw invalid_argument("정점이 존재하지 않는다");
+ // 간선 vet1 - vet2를 추가한다
+ adjList[vet1].push_back(vet2);
+ adjList[vet2].push_back(vet1);
+ }
+
+ /* 간선 삭제 */
+ void removeEdge(Vertex *vet1, Vertex *vet2) {
+ if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2)
+ throw invalid_argument("정점이 존재하지 않는다");
+ // 간선 vet1 - vet2를 삭제한다
+ remove(adjList[vet1], vet2);
+ remove(adjList[vet2], vet1);
+ }
+
+ /* 정점 추가 */
+ void addVertex(Vertex *vet) {
+ if (adjList.count(vet))
+ return;
+ // 인접 리스트에 새 연결 리스트를 추가한다
+ adjList[vet] = vector();
+ }
+
+ /* 정점 삭제 */
+ void removeVertex(Vertex *vet) {
+ if (!adjList.count(vet))
+ throw invalid_argument("정점이 존재하지 않는다");
+ // 인접 리스트에서 정점 vet에 해당하는 연결 리스트를 삭제한다
+ adjList.erase(vet);
+ // 다른 정점의 연결 리스트를 순회하여 vet를 포함하는 모든 간선을 삭제한다
+ for (auto &adj : adjList) {
+ remove(adj.second, vet);
+ }
+ }
+
+ /* 인접 리스트 출력 */
+ void print() {
+ cout << "인접 리스트 =" << endl;
+ for (auto &adj : adjList) {
+ const auto &key = adj.first;
+ const auto &vec = adj.second;
+ cout << key->val << ": ";
+ printVector(vetsToVals(vec));
+ }
+ }
+};
+
+// 테스트 예제는 graph_adjacency_list_test.cpp를 참고한다
diff --git a/ko/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp b/ko/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp
new file mode 100644
index 000000000..3103f0153
--- /dev/null
+++ b/ko/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp
@@ -0,0 +1,49 @@
+/**
+ * File: graph_adjacency_list_test.cpp
+ * Created Time: 2023-02-09
+ * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com)
+ */
+
+#include "./graph_adjacency_list.cpp"
+
+/* 실행 코드 */
+int main() {
+ /* 무방향 그래프 초기화 */
+ vector v = valsToVets(vector{1, 3, 2, 5, 4});
+ vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]},
+ {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}};
+ GraphAdjList graph(edges);
+ cout << "\n초기화한 후의 그래프" << endl;
+ graph.print();
+
+ /* 간선 추가 */
+ // 정점 1, 2는 각각 v[0], v[2]이다
+ graph.addEdge(v[0], v[2]);
+ cout << "\n간선 1-2를 추가한 후의 그래프" << endl;
+ graph.print();
+
+ /* 간선 삭제 */
+ // 정점 1, 3은 각각 v[0], v[1]이다
+ graph.removeEdge(v[0], v[1]);
+ cout << "\n간선 1-3을 삭제한 후의 그래프" << endl;
+ graph.print();
+
+ /* 정점 추가 */
+ Vertex *v5 = new Vertex(6);
+ graph.addVertex(v5);
+ cout << "\n정점 6을 추가한 후의 그래프" << endl;
+ graph.print();
+
+ /* 정점 삭제 */
+ // 정점 3은 v[1]이다
+ graph.removeVertex(v[1]);
+ cout << "\n정점 3을 삭제한 후의 그래프" << endl;
+ graph.print();
+
+ // 메모리 해제
+ for (Vertex *vet : v) {
+ delete vet;
+ }
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp b/ko/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp
new file mode 100644
index 000000000..368fded42
--- /dev/null
+++ b/ko/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp
@@ -0,0 +1,127 @@
+/**
+ * File: graph_adjacency_matrix.cpp
+ * Created Time: 2023-02-09
+ * Author: what-is-me (whatisme@outlook.jp)
+ */
+
+#include "../utils/common.hpp"
+
+/* 인접 행렬로 구현한 무방향 그래프 클래스 */
+class GraphAdjMat {
+ vector vertices; // 정점 리스트의 원소는 정점 값을, 인덱스는 정점 인덱스를 나타낸다
+ vector> adjMat; // 인접 행렬의 행과 열 인덱스는 정점 인덱스에 해당한다
+
+ public:
+ /* 생성자 */
+ GraphAdjMat(const vector &vertices, const vector> &edges) {
+ // 정점 추가
+ for (int val : vertices) {
+ addVertex(val);
+ }
+ // 간선 추가
+ // edges의 원소는 정점 인덱스, 즉 vertices 원소의 인덱스를 나타낸다
+ for (const vector &edge : edges) {
+ addEdge(edge[0], edge[1]);
+ }
+ }
+
+ /* 정점 개수 반환 */
+ int size() const {
+ return vertices.size();
+ }
+
+ /* 정점 추가 */
+ void addVertex(int val) {
+ int n = size();
+ // 정점 리스트에 새 정점의 값을 추가한다
+ vertices.push_back(val);
+ // 인접 행렬에 행 하나를 추가한다
+ adjMat.emplace_back(vector(n, 0));
+ // 인접 행렬에 열 하나를 추가한다
+ for (vector &row : adjMat) {
+ row.push_back(0);
+ }
+ }
+
+ /* 정점 삭제 */
+ void removeVertex(int index) {
+ if (index >= size()) {
+ throw out_of_range("정점이 존재하지 않는다");
+ }
+ // 정점 리스트에서 인덱스 index의 정점을 제거한다
+ vertices.erase(vertices.begin() + index);
+ // 인접 행렬에서 인덱스 index의 행을 삭제한다
+ adjMat.erase(adjMat.begin() + index);
+ // 인접 행렬에서 인덱스 index의 열을 삭제한다
+ for (vector &row : adjMat) {
+ row.erase(row.begin() + index);
+ }
+ }
+
+ /* 간선 추가 */
+ // 매개변수 i, j는 vertices 원소의 인덱스에 해당한다
+ void addEdge(int i, int j) {
+ // 인덱스가 범위를 벗어나거나 서로 같은 경우를 처리한다
+ if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {
+ throw out_of_range("정점이 존재하지 않는다");
+ }
+ // 무방향 그래프의 인접 행렬은 주대각선에 대해 대칭이며, (i, j) == (j, i)를 만족한다
+ adjMat[i][j] = 1;
+ adjMat[j][i] = 1;
+ }
+
+ /* 간선 삭제 */
+ // 매개변수 i, j는 vertices 원소의 인덱스에 해당한다
+ void removeEdge(int i, int j) {
+ // 인덱스가 범위를 벗어나거나 서로 같은 경우를 처리한다
+ if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) {
+ throw out_of_range("정점이 존재하지 않는다");
+ }
+ adjMat[i][j] = 0;
+ adjMat[j][i] = 0;
+ }
+
+ /* 인접 행렬 출력 */
+ void print() {
+ cout << "정점 리스트 = ";
+ printVector(vertices);
+ cout << "인접 행렬 =" << endl;
+ printVectorMatrix(adjMat);
+ }
+};
+
+/* 실행 코드 */
+int main() {
+ /* 무방향 그래프 초기화 */
+ // edges의 원소는 정점 인덱스, 즉 vertices 원소의 인덱스를 나타낸다
+ vector vertices = {1, 3, 2, 5, 4};
+ vector> edges = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}};
+ GraphAdjMat graph(vertices, edges);
+ cout << "\n초기화한 후의 그래프" << endl;
+ graph.print();
+
+ /* 간선 추가 */
+ // 정점 1, 2의 인덱스는 각각 0, 2이다
+ graph.addEdge(0, 2);
+ cout << "\n간선 1-2를 추가한 후의 그래프" << endl;
+ graph.print();
+
+ /* 간선 삭제 */
+ // 정점 1, 3의 인덱스는 각각 0, 1이다
+ graph.removeEdge(0, 1);
+ cout << "\n간선 1-3을 삭제한 후의 그래프" << endl;
+ graph.print();
+
+ /* 정점 추가 */
+ graph.addVertex(6);
+ cout << "\n정점 6을 추가한 후의 그래프" << endl;
+ graph.print();
+
+ /* 정점 삭제 */
+ // 정점 3의 인덱스는 1이다
+ graph.removeVertex(1);
+ cout << "\n정점 3을 삭제한 후의 그래프" << endl;
+ graph.print();
+
+ return 0;
+}
diff --git a/ko/codes/cpp/chapter_graph/graph_bfs.cpp b/ko/codes/cpp/chapter_graph/graph_bfs.cpp
new file mode 100644
index 000000000..0e3c076a2
--- /dev/null
+++ b/ko/codes/cpp/chapter_graph/graph_bfs.cpp
@@ -0,0 +1,59 @@
+/**
+ * File: graph_bfs.cpp
+ * Created Time: 2023-03-02
+ * Author: krahets (krahets@163.com)
+ */
+
+#include "../utils/common.hpp"
+#include "./graph_adjacency_list.cpp"
+
+/* 너비 우선 순회 */
+// 지정한 정점의 모든 인접 정점을 얻기 쉽도록 그래프를 인접 리스트로 표현한다
+vector graphBFS(GraphAdjList &graph, Vertex *startVet) {
+ // 정점 순회 순서
+ vector res;
+ // 방문한 정점을 기록하는 해시 집합
+ unordered_set visited = {startVet};
+ // 큐를 사용하여 BFS를 구현한다
+ queue que;
+ que.push(startVet);
+ // 정점 vet에서 시작하여 모든 정점을 방문할 때까지 반복한다
+ while (!que.empty()) {
+ Vertex *vet = que.front();
+ que.pop(); // 큐 맨 앞의 정점을 꺼낸다
+ res.push_back(vet); // 방문한 정점을 기록한다
+ // 해당 정점의 모든 인접 정점을 순회한다
+ for (auto adjVet : graph.adjList[vet]) {
+ if (visited.count(adjVet))
+ continue; // 이미 방문한 정점은 건너뛴다
+ que.push(adjVet); // 방문하지 않은 정점만 큐에 삽입한다
+ visited.emplace(adjVet); // 해당 정점을 방문했다고 표시한다
+ }
+ }
+ // 정점 순회 순서를 반환한다
+ return res;
+}
+
+/* 실행 코드 */
+int main() {
+ /* 무방향 그래프 초기화 */
+ vector v = valsToVets({0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
+ vector