feat(ko): add the Korean online edition (#2006)

* feat(ko): add the complete Korean online edition

* Use UTF-8 for Python CI output on Windows

* Remove print-book promotion from the Korean site

* fix(ko): preserve emphasis before Korean particles
This commit is contained in:
Yudong Jin
2026-10-29 03:46:43 +08:00
committed by GitHub
parent 71a773df64
commit 2f561d3d51
2072 changed files with 110884 additions and 102 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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 }}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"]

View File

@@ -47,6 +47,8 @@
<a href="https://github.com/krahets/hello-algo/blob/main/ja/README.md">日本語</a>
|
<a href="https://github.com/krahets/hello-algo/blob/main/ru/README.md">Русский</a>
|
<a href="https://github.com/krahets/hello-algo/blob/main/ko/README.md">한국어</a>
</p>
## 关于本书

View File

@@ -47,6 +47,8 @@
<a href="https://github.com/krahets/hello-algo/blob/main/ja/README.md">日本語</a>
|
<a href="https://github.com/krahets/hello-algo/blob/main/ru/README.md">Русский</a>
|
<a href="https://github.com/krahets/hello-algo/blob/main/ko/README.md">한국어</a>
</p>
## The book

View File

@@ -47,6 +47,8 @@
日本語
|
<a href="https://github.com/krahets/hello-algo/blob/main/ru/README.md">Русский</a>
|
<a href="https://github.com/krahets/hello-algo/blob/main/ko/README.md">한국어</a>
</p>
## この本について

96
ko/README.md Normal file
View File

@@ -0,0 +1,96 @@
<p align="center">
<a href="https://www.hello-algo.com/ko/">
<img src="/ko/docs/index.assets/hello_algo_header.png" width="450"></a>
</p>
<p align="center">
<img style="height: 60px;" src="https://readme-typing-svg.demolab.com?font=Noto+Sans+KR&weight=400&duration=3500&pause=2000&color=21C8B8&center=true&vCenter=true&random=false&width=200&lines=Hello%2C%20%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%21" alt="Hello 알고리즘 타이핑 애니메이션" />
</br>
애니메이션으로 설명하고 클릭 한 번으로 실행하는 자료 구조와 알고리즘 입문서
</p>
<p align="center">
<a href="https://www.hello-algo.com/ko/">
<strong>온라인으로 읽기</strong></a>
<a href="https://github.com/krahets/hello-algo/releases">
<strong>PDF/EPUB 제공 언어 확인</strong></a>
</p>
<p align="center">한국어판은 온라인으로 읽을 수 있다. PDF/EPUB의 제공 언어는 릴리스 페이지에서 확인할 수 있다.</p>
<p align="center">
<img src="/ko/docs/index.assets/animation.gif" width="395">
<img src="/ko/docs/index.assets/running_code.gif" width="395">
</p>
<p align="center">
<img src="https://img.shields.io/badge/Python-snow?logo=python&logoColor=3776AB" alt="Python" />
<img src="https://img.shields.io/badge/Java-snow?logo=coffeescript&logoColor=FC4C02" alt="Java" />
<img src="https://img.shields.io/badge/C%2B%2B-snow?logo=c%2B%2B&logoColor=00599C" alt="C++" />
<img src="https://img.shields.io/badge/C-snow?logo=c&logoColor=A8B9CC" alt="C" />
<img src="https://img.shields.io/badge/C%23-snow?logo=csharp&logoColor=512BD4" alt="C#" />
<img src="https://img.shields.io/badge/JavaScript-snow?logo=javascript&logoColor=E9CE30" alt="JavaScript" />
<img src="https://img.shields.io/badge/Go-snow?logo=go&logoColor=00ADD8" alt="Go" />
<img src="https://img.shields.io/badge/Swift-snow?logo=swift&logoColor=F05138" alt="Swift" />
<img src="https://img.shields.io/badge/Rust-snow?logo=rust&logoColor=000000" alt="Rust" />
<img src="https://img.shields.io/badge/Ruby-snow?logo=ruby&logoColor=CC342D" alt="Ruby" />
<img src="https://img.shields.io/badge/Kotlin-snow?logo=kotlin&logoColor=7F52FF" alt="Kotlin" />
<img src="https://img.shields.io/badge/TypeScript-snow?logo=typescript&logoColor=3178C6" alt="TypeScript" />
<img src="https://img.shields.io/badge/Dart-snow?logo=dart&logoColor=0175C2" alt="Dart" />
</p>
<p align="center">
<a href="https://github.com/krahets/hello-algo/blob/main/README.md">简体中文</a>
|
<a href="https://github.com/krahets/hello-algo/blob/main/zh-hant/README.md">繁體中文</a>
|
<a href="https://github.com/krahets/hello-algo/blob/main/en/README.md">English</a>
|
<a href="https://github.com/krahets/hello-algo/blob/main/ja/README.md">日本語</a>
|
<a href="https://github.com/krahets/hello-algo/blob/main/ru/README.md">Русский</a>
|
한국어
</p>
## 이 책에 대하여
이 프로젝트는 오픈 소스로 공개되는 무료 자료 구조와 알고리즘 입문서를 만드는 것을 목표로 한다. 처음 배우는 사람도 쉽게 접근할 수 있는 책을 지향한다.
- 책 전체에 애니메이션 그림을 사용하여 내용을 명확하고 이해하기 쉽게 설명한다. 난이도를 완만하게 높여 가며 초보자가 자료 구조와 알고리즘의 지식 지도를 탐험하도록 안내한다.
- 소스 코드를 클릭 한 번으로 실행할 수 있다. 독자가 실습을 통해 프로그래밍 능력을 키우고 알고리즘의 동작 원리와 자료 구조의 내부 구현을 이해하도록 돕는다.
- 독자들이 서로 도우며 공부하기를 권장한다. 댓글에서 질문하고 의견을 나누며 함께 성장할 수 있다.
이 책이 도움이 되었다면 페이지 오른쪽 위의 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`로 연락하기 바란다.
이 책을 더 좋은 책으로 만들어 준 모든 집필 참여자의 아낌없는 기여에 감사드린다. 참여자는 다음과 같다:
<p align="left">
<a href="https://github.com/krahets/hello-algo/graphs/contributors">
<img width="770" src="https://contrib.rocks/image?repo=krahets/hello-algo&max=300&columns=16" />
</a>
</p>
## 라이선스
이 저장소의 글, 코드, 그림, 사진 및 영상에는 [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) 라이선스가 적용된다.

35
ko/codes/Dockerfile Normal file
View File

@@ -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"]

9
ko/codes/c/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
# 모든 항목을 무시한다
*
# 확장자가 있는 모든 항목을 무시 대상에서 제외한다
!*.*
# 모든 디렉터리를 무시 대상에서 제외한다
!*/
*.dSYM/
build/

20
ko/codes/c/CMakeLists.txt Normal file
View File

@@ -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)

View File

@@ -0,0 +1,3 @@
add_executable(array array.c)
add_executable(linked_list linked_list.c)
add_executable(my_list my_list.c)

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -0,0 +1,61 @@
/**
* File: complexity_exercises.c
* Created Time: 2026-08-18
* Author: Hello Algo Team
*/
#include <assert.h>
/* 반복으로 합 구하기 */
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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,26 @@
/**
* File: fast_power.c
* Created Time: 2026-08-18
* Author: Hello Algo Team
*/
#include <assert.h>
/* 빠른 거듭제곱 */
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;
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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");
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -0,0 +1,213 @@
/**
* File: hash_map_chaining.c
* Created Time: 2023-10-13
* Author: SenMing (1206575349@qq.com), krahets (krahets@163.com)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,2 @@
add_executable(my_heap_test my_heap_test.c)
add_executable(top_k top_k.c)

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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;
}

Some files were not shown because too many files have changed in this diff Show More